aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/src/main/kotlin/DokkaDescriptorVisitor.kt90
-rw-r--r--core/src/main/kotlin/Model/DocumentationNode.kt101
-rw-r--r--core/src/main/kotlin/Model/transformers/DocumentationNodesMerger.kt38
-rw-r--r--core/src/main/kotlin/Utilities/nodeDebug.kt2
-rw-r--r--core/src/main/kotlin/links/DRI.kt72
-rw-r--r--core/src/main/kotlin/pages/PageBuilder.kt95
-rw-r--r--core/src/main/kotlin/pages/PageContentBuilder.kt206
-rw-r--r--core/src/main/kotlin/pages/PageNodes.kt20
-rw-r--r--core/src/main/kotlin/transformers/DefaultDocumentationToPageTransformer.kt228
9 files changed, 527 insertions, 325 deletions
diff --git a/core/src/main/kotlin/DokkaDescriptorVisitor.kt b/core/src/main/kotlin/DokkaDescriptorVisitor.kt
index c24c43aa..bb914a27 100644
--- a/core/src/main/kotlin/DokkaDescriptorVisitor.kt
+++ b/core/src/main/kotlin/DokkaDescriptorVisitor.kt
@@ -1,26 +1,29 @@
package org.jetbrains.dokka
import org.jetbrains.dokka.Model.*
+import org.jetbrains.dokka.Model.ClassKind
import org.jetbrains.dokka.Model.Function
import org.jetbrains.dokka.links.Callable
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.links.withClass
import org.jetbrains.dokka.pages.PlatformData
import org.jetbrains.kotlin.descriptors.*
-import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.FAKE_OVERRIDE
-import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.SYNTHESIZED
import org.jetbrains.kotlin.descriptors.impl.DeclarationDescriptorVisitorEmptyBodies
import org.jetbrains.kotlin.idea.kdoc.findKDoc
import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink
import org.jetbrains.kotlin.kdoc.psi.impl.KDocLink
import org.jetbrains.kotlin.kdoc.psi.impl.KDocName
+import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
+import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperclassesWithoutAny
+import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.MemberScope
+import org.jetbrains.kotlin.types.KotlinType
class DokkaDescriptorVisitor(
- val platformData: PlatformData,
+ private val platformData: PlatformData,
private val resolutionFacade: DokkaResolutionFacade
-) : DeclarationDescriptorVisitorEmptyBodies<DocumentationNode<*>, DRI>() {
+) : DeclarationDescriptorVisitorEmptyBodies<DocumentationNode, DRI>() {
override fun visitDeclarationDescriptor(descriptor: DeclarationDescriptor, parent: DRI): Nothing {
throw IllegalStateException("${javaClass.simpleName} should never enter ${descriptor.javaClass.simpleName}")
}
@@ -42,17 +45,18 @@ class DokkaDescriptorVisitor(
override fun visitClassDescriptor(descriptor: ClassDescriptor, parent: DRI): Class {
val dri = parent.withClass(descriptor.name.asString())
val scope = descriptor.getMemberScope(emptyList())
-
+ val descriptorData = descriptor.takeUnless { it.isExpect }?.resolveClassDescriptionData()
return Class(
dri,
descriptor.name.asString(),
+ KotlinClassKindTypes.valueOf(descriptor.kind.toString()),
descriptor.constructors.map { visitConstructorDescriptor(it, dri) },
scope.functions(dri),
scope.properties(dri),
scope.classes(dri),
- descriptor.takeIf { it.isExpect }?.resolveDescriptorData(),
- listOfNotNull(descriptor.takeUnless { it.isExpect }?.resolveDescriptorData()),
- getXMLDRIs(listOfNotNull(descriptor.takeUnless { it.isExpect }?.resolveDescriptorData())).toMutableSet()
+ descriptor.takeIf { it.isExpect }?.resolveClassDescriptionData(),
+ listOfNotNull(descriptorData),
+ getXMLDRIs(descriptor, descriptorData).toMutableSet()
)
}
@@ -72,6 +76,8 @@ class DokkaDescriptorVisitor(
return Function(
dri,
descriptor.name.asString(),
+ descriptor.returnType?.let { KotlinTypeWrapper(it) },
+ false,
descriptor.extensionReceiverParameter?.let { visitReceiverParameterDescriptor(it, dri) },
descriptor.valueParameters.mapIndexed { index, desc -> parameter(index, desc, dri) },
descriptor.takeIf { it.isExpect }?.resolveDescriptorData(),
@@ -84,6 +90,8 @@ class DokkaDescriptorVisitor(
return Function(
dri,
"<init>",
+ KotlinTypeWrapper(descriptor.returnType),
+ true,
null,
descriptor.valueParameters.mapIndexed { index, desc -> parameter(index, desc, dri) },
descriptor.takeIf { it.isExpect }?.resolveDescriptorData(),
@@ -97,6 +105,7 @@ class DokkaDescriptorVisitor(
) = Parameter(
parent.copy(target = 0),
null,
+ KotlinTypeWrapper(descriptor.type),
listOf(descriptor.resolveDescriptorData())
)
@@ -104,16 +113,13 @@ class DokkaDescriptorVisitor(
Parameter(
parent.copy(target = index + 1),
descriptor.name.asString(),
+ KotlinTypeWrapper(descriptor.type),
listOf(descriptor.resolveDescriptorData())
)
- private val FunctionDescriptor.isSynthetic: Boolean
- get() = (kind == FAKE_OVERRIDE || kind == SYNTHESIZED) && findKDoc() == null
-
private fun MemberScope.functions(parent: DRI): List<Function> =
getContributedDescriptors(DescriptorKindFilter.FUNCTIONS) { true }
.filterIsInstance<FunctionDescriptor>()
- .filterNot { it.isSynthetic }
.map { visitFunctionDescriptor(it, parent) }
private fun MemberScope.properties(parent: DRI): List<Property> =
@@ -126,7 +132,7 @@ class DokkaDescriptorVisitor(
.filterIsInstance<ClassDescriptor>()
.map { visitClassDescriptor(it, parent) }
- private fun <T : DeclarationDescriptor> T.resolveDescriptorData(): Descriptor<T> {
+ private fun DeclarationDescriptor.resolveDescriptorData(): PlatformInfo {
val doc = findKDoc()
val links = doc?.children?.filter { it is KDocLink }?.flatMap { link ->
val destination = link.children.first { it is KDocName }.text
@@ -138,26 +144,48 @@ class DokkaDescriptorVisitor(
destination.split('.')
).map { Pair(destination, DRI.from(it)) }
}?.toMap() ?: emptyMap()
- return Descriptor(this, doc, links, listOf(platformData))
+ return BasePlatformInfo(doc, links, listOf(platformData))
}
- private fun getXMLDRIs(descriptors: List<Descriptor<*>>) =
- descriptors.flatMap {
- it.docTag?.children
- ?.filter {
- it.text.contains("@attr")
- }.orEmpty()
- }.flatMap { ref ->
- val matchResult = "@attr\\s+ref\\s+(.+)".toRegex().matchEntire(ref.text)
- val toFind = matchResult?.groups?.last()?.value.orEmpty()
- resolveKDocLink(
- resolutionFacade.resolveSession.bindingContext,
- resolutionFacade,
- descriptors.first().descriptor,
- null,
- toFind.split('.')
- ).map { DefaultExtra("@attr ref", DRI.from(it).toString()) }
- }
+ private fun ClassDescriptor.resolveClassDescriptionData(): ClassPlatformInfo {
+ return ClassPlatformInfo(resolveDescriptorData(),
+ (getSuperInterfaces() + getAllSuperclassesWithoutAny()).map { DRI.from(it) })
+ }
+
+ private fun getXMLDRIs(descriptor: DeclarationDescriptor, platformInfo: PlatformInfo?) =
+ platformInfo?.docTag?.children
+ ?.filter {
+ it.text.contains("@attr")
+ }?.flatMap { ref ->
+ val matchResult = "@attr\\s+ref\\s+(.+)".toRegex().matchEntire(ref.text)
+ val toFind = matchResult?.groups?.last()?.value.orEmpty()
+ resolveKDocLink(
+ resolutionFacade.resolveSession.bindingContext,
+ resolutionFacade,
+ descriptor,
+ null,
+ toFind.split('.')
+ ).map { DefaultExtra("@attr ref", DRI.from(it).toString()) }
+ }.orEmpty()
}
data class DefaultExtra(val key: String, val value: String) : Extra
+
+enum class KotlinClassKindTypes : ClassKind {
+ CLASS,
+ INTERFACE,
+ ENUM_CLASS,
+ ENUM_ENTRY,
+ ANNOTATION_CLASS,
+ OBJECT;
+}
+
+class KotlinTypeWrapper(private val kotlinType: KotlinType) : TypeWrapper {
+ private val declarationDescriptor = kotlinType.constructor.declarationDescriptor
+ private val fqNameSafe = declarationDescriptor?.fqNameSafe
+ override val constructorFqName = fqNameSafe?.asString()
+ override val constructorNamePathSegments: List<String> =
+ fqNameSafe?.pathSegments()?.map { it.asString() } ?: emptyList()
+ override val arguments: List<KotlinTypeWrapper> by lazy { kotlinType.arguments.map { KotlinTypeWrapper(it.type) } }
+ override val dri: DRI? by lazy { declarationDescriptor?.let { DRI.from(it) } }
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Model/DocumentationNode.kt b/core/src/main/kotlin/Model/DocumentationNode.kt
index 55ae5902..623f2ea3 100644
--- a/core/src/main/kotlin/Model/DocumentationNode.kt
+++ b/core/src/main/kotlin/Model/DocumentationNode.kt
@@ -1,11 +1,11 @@
package org.jetbrains.dokka.Model
+import org.jetbrains.dokka.KotlinTypeWrapper
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.pages.PlatformData
-import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
-class Module(val packages: List<Package>) : DocumentationNode<Nothing>() {
+class Module(val packages: List<Package>) : DocumentationNode() {
override val dri: DRI = DRI.topLevel
override val children: List<Package> = packages
override val extra: MutableSet<Extra> = mutableSetOf()
@@ -17,31 +17,36 @@ class Package(
override val properties: List<Property>,
override val classes: List<Class>,
override val extra: MutableSet<Extra> = mutableSetOf()
-) : ScopeNode<Nothing>() {
+) : ScopeNode() {
val name = dri.packageName.orEmpty()
}
class Class(
override val dri: DRI,
val name: String,
+ val kind: ClassKind,
val constructors: List<Function>,
override val functions: List<Function>,
override val properties: List<Property>,
override val classes: List<Class>,
- override val expectDescriptor: Descriptor<ClassDescriptor>?,
- override val actualDescriptors: List<Descriptor<ClassDescriptor>>,
+ override val expected: ClassPlatformInfo?,
+ override val actual: List<ClassPlatformInfo>,
override val extra: MutableSet<Extra> = mutableSetOf()
-) : ScopeNode<ClassDescriptor>()
+) : ScopeNode() {
+ val inherited by lazy { platformInfo.mapNotNull { (it as? ClassPlatformInfo)?.inherited }.flatten() }
+}
class Function(
override val dri: DRI,
val name: String,
+ val returnType: TypeWrapper?,
+ val isConstructor: Boolean,
override val receiver: Parameter?,
val parameters: List<Parameter>,
- override val expectDescriptor: Descriptor<FunctionDescriptor>?,
- override val actualDescriptors: List<Descriptor<FunctionDescriptor>>,
+ override val expected: PlatformInfo?,
+ override val actual: List<PlatformInfo>,
override val extra: MutableSet<Extra> = mutableSetOf()
-) : CallableNode<FunctionDescriptor>() {
+) : CallableNode() {
override val children: List<Parameter>
get() = listOfNotNull(receiver) + parameters
}
@@ -50,10 +55,10 @@ class Property(
override val dri: DRI,
val name: String,
override val receiver: Parameter?,
- override val expectDescriptor: Descriptor<PropertyDescriptor>?,
- override val actualDescriptors: List<Descriptor<PropertyDescriptor>>,
+ override val expected: PlatformInfo?,
+ override val actual: List<PlatformInfo>,
override val extra: MutableSet<Extra> = mutableSetOf()
-) : CallableNode<PropertyDescriptor>() {
+) : CallableNode() {
override val children: List<Parameter>
get() = listOfNotNull(receiver)
}
@@ -62,67 +67,75 @@ class Property(
class Parameter(
override val dri: DRI,
val name: String?,
- override val actualDescriptors: List<Descriptor<ParameterDescriptor>>,
+ val type: TypeWrapper,
+ override val actual: List<PlatformInfo>,
override val extra: MutableSet<Extra> = mutableSetOf()
-) : DocumentationNode<ParameterDescriptor>() {
- override val children: List<DocumentationNode<*>>
+) : DocumentationNode() {
+ override val children: List<DocumentationNode>
get() = emptyList()
}
-class Descriptor<out T : DeclarationDescriptor>(
- val descriptor: T,
- val docTag: KDocTag?,
- val links: Map<String, DRI>,
+interface PlatformInfo {
+ val docTag: KDocTag?
+ val links: Map<String, DRI>
val platformData: List<PlatformData>
-) : DeclarationDescriptor by descriptor {
+}
+
+class BasePlatformInfo(
+ override val docTag: KDocTag?,
+ override val links: Map<String, DRI>,
+ override val platformData: List<PlatformData>) : PlatformInfo {
override fun equals(other: Any?): Boolean =
- other is Descriptor<*> && (
- descriptor.toString() == other.descriptor.toString() &&
- docTag?.text == other.docTag?.text &&
+ other is PlatformInfo && (
+ docTag?.text == other.docTag?.text &&
links == other.links)
override fun hashCode(): Int =
- listOf(descriptor.toString(), docTag?.text, links).hashCode()
+ listOf(docTag?.text, links).hashCode()
}
-abstract class DocumentationNode<out T : DeclarationDescriptor> {
- open val expectDescriptor: Descriptor<T>? = null
- open val actualDescriptors: List<Descriptor<T>> = emptyList()
- val descriptors by lazy { listOfNotNull(expectDescriptor) + actualDescriptors }
- val platformData by lazy { descriptors.flatMap { it.platformData }.toSet() }
+class ClassPlatformInfo(
+ val info: PlatformInfo,
+ val inherited: List<DRI>) : PlatformInfo by info
+
+abstract class DocumentationNode {
+ open val expected: PlatformInfo? = null
+ open val actual: List<PlatformInfo> = emptyList()
+ val platformInfo by lazy { listOfNotNull(expected) + actual }
+ val platformData by lazy { platformInfo.flatMap { it.platformData }.toSet() }
abstract val dri: DRI
- abstract val children: List<DocumentationNode<*>>
+ abstract val children: List<DocumentationNode>
override fun toString(): String {
return "${javaClass.simpleName}($dri)" + briefDocstring.takeIf { it.isNotBlank() }?.let { " [$it]" }.orEmpty()
}
- override fun equals(other: Any?) = other is DocumentationNode<*> && this.dri == other.dri
+ override fun equals(other: Any?) = other is DocumentationNode && this.dri == other.dri
override fun hashCode() = dri.hashCode()
val commentsData: List<Pair<String, Map<String, DRI>>>
- get() = descriptors.mapNotNull { it.docTag?.let { tag -> Pair(tag.getContent(), it.links) } }
+ get() = platformInfo.mapNotNull { it.docTag?.let { tag -> Pair(tag.getContent(), it.links) } }
val briefDocstring: String
- get() = descriptors.firstOrNull()?.docTag?.getContent().orEmpty().shorten(40)
+ get() = platformInfo.firstOrNull()?.docTag?.getContent().orEmpty().shorten(40)
open val extra: MutableSet<Extra> = mutableSetOf()
}
-abstract class ScopeNode<out T : ClassOrPackageFragmentDescriptor> : DocumentationNode<T>() {
+abstract class ScopeNode : DocumentationNode() {
abstract val functions: List<Function>
abstract val properties: List<Property>
abstract val classes: List<Class>
- override val children: List<DocumentationNode<MemberDescriptor>>
+ override val children: List<DocumentationNode>
get() = functions + properties + classes
}
-abstract class CallableNode<out T : CallableDescriptor> : DocumentationNode<T>() {
+abstract class CallableNode : DocumentationNode() {
abstract val receiver: Parameter?
}
@@ -130,20 +143,28 @@ private fun String.shorten(maxLength: Int) = lineSequence().first().let {
if (it.length != length || it.length > maxLength) it.take(maxLength - 3) + "..." else it
}
-fun DocumentationNode<*>.walk(process: DocumentationNode<*>.() -> Unit) {
+interface TypeWrapper {
+ val constructorFqName: String?
+ val constructorNamePathSegments: List<String>
+ val arguments: List<KotlinTypeWrapper>
+ val dri: DRI?
+}
+interface ClassKind
+
+fun DocumentationNode.walk(process: DocumentationNode.() -> Unit) {
this.process()
this.children.forEach { it.process() }
}
-fun DocumentationNode<*>.dfs(predicate: (DocumentationNode<*>) -> Boolean): DocumentationNode<*>? =
+fun DocumentationNode.dfs(predicate: (DocumentationNode) -> Boolean): DocumentationNode? =
if (predicate(this)) {
this
} else {
this.children.asSequence().mapNotNull { it.dfs(predicate) }.firstOrNull()
}
-fun DocumentationNode<*>.findAll(predicate: (DocumentationNode<*>) -> Boolean): Set<DocumentationNode<*>> {
- val found = mutableSetOf<DocumentationNode<*>>()
+fun DocumentationNode.findAll(predicate: (DocumentationNode) -> Boolean): Set<DocumentationNode> {
+ val found = mutableSetOf<DocumentationNode>()
if (predicate(this)) {
found.add(this)
} else {
diff --git a/core/src/main/kotlin/Model/transformers/DocumentationNodesMerger.kt b/core/src/main/kotlin/Model/transformers/DocumentationNodesMerger.kt
index 2fb2f7c0..ae4f8d99 100644
--- a/core/src/main/kotlin/Model/transformers/DocumentationNodesMerger.kt
+++ b/core/src/main/kotlin/Model/transformers/DocumentationNodesMerger.kt
@@ -2,7 +2,6 @@ package org.jetbrains.dokka.Model.transformers
import org.jetbrains.dokka.Model.*
import org.jetbrains.dokka.Model.Function
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
internal object DocumentationNodesMerger : DocumentationNodeTransformer {
override fun invoke(original: Module) = Module(
@@ -19,55 +18,68 @@ private fun mergePackageContent(original: Package) = Package(
merge(original.classes, Class::mergeWith)
)
-private fun <T: DocumentationNode<*>> merge(elements: List<T>, reducer: (T, T) -> T): List<T> =
+private fun <T: DocumentationNode> merge(elements: List<T>, reducer: (T, T) -> T): List<T> =
elements.groupingBy { it.dri }
.reduce { _, left, right -> reducer(left, right)}
.values.toList()
-fun <T:DeclarationDescriptor> Descriptor<T>.mergeWith(other: Descriptor<T>?) = Descriptor(
- descriptor,
+fun PlatformInfo.mergeWith(other: PlatformInfo?) = BasePlatformInfo(
docTag,
links,
(platformData + (other?.platformData ?: emptyList())).distinct()
)
-fun <T:DeclarationDescriptor> List<Descriptor<T>>.merge() : List<Descriptor<T>> =
- groupingBy { it.descriptor }.reduce {
+fun ClassPlatformInfo.mergeWith(other: ClassPlatformInfo?) = ClassPlatformInfo(
+ info.mergeWith(other?.info),
+ (inherited + (other?.inherited ?: emptyList())).distinct()
+)
+
+fun List<ClassPlatformInfo>.mergeClassPlatformInfo() : List<ClassPlatformInfo> =
+ groupingBy { it.docTag.toString() + it.links + it.inherited}.reduce {
+ _, left, right -> left.mergeWith(right)
+ }.values.toList()
+
+fun List<PlatformInfo>.merge() : List<PlatformInfo> =
+ groupingBy { it.docTag.toString() + it.links }.reduce {
_, left, right -> left.mergeWith(right)
}.values.toList()
fun Function.mergeWith(other: Function) = Function(
dri,
name,
+ returnType,
+ isConstructor,
if (receiver != null && other.receiver != null) receiver.mergeWith(other.receiver) else null,
merge(parameters + other.parameters, Parameter::mergeWith),
- expectDescriptor?.mergeWith(other.expectDescriptor),
- (actualDescriptors + other.actualDescriptors).merge()
+ expected?.mergeWith(other.expected),
+ (actual + other.actual).merge()
)
fun Property.mergeWith(other: Property) = Property(
dri,
name,
if (receiver != null && other.receiver != null) receiver.mergeWith(other.receiver) else null,
- expectDescriptor?.mergeWith(other.expectDescriptor),
- (actualDescriptors + other.actualDescriptors).merge()
+ expected?.mergeWith(other.expected),
+ (actual + other.actual).merge()
)
fun Class.mergeWith(other: Class) = Class(
dri,
name,
+ kind,
merge(constructors + other.constructors, Function::mergeWith),
merge(functions + other.functions, Function::mergeWith),
merge(properties + other.properties, Property::mergeWith),
merge(classes + other.classes, Class::mergeWith),
- expectDescriptor?.mergeWith(other.expectDescriptor),
- (actualDescriptors + other.actualDescriptors).merge()
+ expected?.mergeWith(other.expected),
+ (actual + other.actual).mergeClassPlatformInfo()
)
fun Parameter.mergeWith(other: Parameter) = Parameter(
dri,
name,
- (actualDescriptors + other.actualDescriptors).merge()
+ type,
+ (actual + other.actual).merge()
)
fun Package.mergeWith(other: Package) = Package(
diff --git a/core/src/main/kotlin/Utilities/nodeDebug.kt b/core/src/main/kotlin/Utilities/nodeDebug.kt
index 37655b0b..e89f88ec 100644
--- a/core/src/main/kotlin/Utilities/nodeDebug.kt
+++ b/core/src/main/kotlin/Utilities/nodeDebug.kt
@@ -8,7 +8,7 @@ const val DOWN = '\u2503'
const val BRANCH = '\u2523'
const val LAST = '\u2517'
-fun <T : DeclarationDescriptor> DocumentationNode<T>.pretty(prefix: String = "", isLast: Boolean = true): String {
+fun DocumentationNode.pretty(prefix: String = "", isLast: Boolean = true): String {
val nextPrefix = prefix + (if (isLast) ' ' else DOWN) + ' '
return prefix + (if (isLast) LAST else BRANCH) + this.toString() +
diff --git a/core/src/main/kotlin/links/DRI.kt b/core/src/main/kotlin/links/DRI.kt
index 605840a7..845388b5 100644
--- a/core/src/main/kotlin/links/DRI.kt
+++ b/core/src/main/kotlin/links/DRI.kt
@@ -1,8 +1,11 @@
package org.jetbrains.dokka.links
import org.jetbrains.kotlin.descriptors.*
+import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.parentsWithSelf
+import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver
import org.jetbrains.kotlin.types.KotlinType
+import org.jetbrains.kotlin.types.TypeProjection
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.text.ParseException
@@ -47,7 +50,7 @@ data class DRI(
)
}
} catch (e: Throwable) {
- throw ParseException(s, 0)
+ throw ParseException("Can not create DRI from $s", 0)
}
fun from(descriptor: DeclarationDescriptor) = descriptor.parentsWithSelf.run {
@@ -67,7 +70,7 @@ data class DRI(
}
}
-fun DRI.withClass(name: String) = copy(classNames = if(classNames.isNullOrBlank()) name else "$classNames.$name")
+fun DRI.withClass(name: String) = copy(classNames = if (classNames.isNullOrBlank()) name else "$classNames.$name")
val DRI.parent: DRI
get() = when {
@@ -78,7 +81,12 @@ val DRI.parent: DRI
else -> DRI.topLevel
}
-data class Callable(val name: String, val receiver: String, val returnType: String, val params: List<String>) {
+data class Callable(
+ val name: String,
+ val receiver: ClassReference? = null,
+ val returnType: String,
+ val params: List<ClassReference>
+) {
fun signature() = "$receiver#$returnType#${params.joinToString("#")}"
companion object {
@@ -88,9 +96,9 @@ data class Callable(val name: String, val receiver: String, val returnType: Stri
.let { (receiver, returnType, params) ->
Callable(
name.toString(),
- receiver,
+ ClassReference.from(receiver),
returnType,
- params.split('#').filter { it.isNotBlank() }
+ params.split('#').mapNotNull { if (it.isNotBlank()) ClassReference.from(it) else null }
)
}
} catch (e: Throwable) {
@@ -106,17 +114,61 @@ data class Callable(val name: String, val receiver: String, val returnType: Stri
fun from(descriptor: CallableDescriptor) = with(descriptor) {
Callable(
name.asString(),
- extensionReceiverParameter?.value?.type?.constructorName.orEmpty(),
+ extensionReceiverParameter?.let { ClassReference.from(it) },
returnType?.constructorName.orEmpty(),
- valueParameters.map { it.type.constructorName.orEmpty() }
+ valueParameters.map { ClassReference.from(it.type.constructorName.orEmpty()) }
)
}
}
}
-data class ClassReference(val dri: DRI, val subs: MutableList<ClassReference> = mutableListOf()) {
- private val subsText = subs.takeIf { it.isNotEmpty() }?.toString().orEmpty()
- override fun toString() = "$dri$subsText"
+data class ClassReference(val classNames: String, val typeBounds: List<ClassReference> = emptyList()) {
+ override fun toString() = classNames + if (typeBounds.isNotEmpty()) {
+ "[${typeBounds.joinToString(",")}]"
+ } else {
+ ""
+ }
+
+ companion object {
+
+ fun from(s: String?): ClassReference =
+ s?.let {
+ "((?:\\w+\\.?)+)(?:\\[((?:\\w+,?)+)])?".toRegex() // This regex matches class names with or without typebounds
+ .matchEntire(it)
+ ?.let { m ->
+ ClassReference(m.groupValues[1], typeBoundsFrom(m.groupValues[2]))
+ }
+ } ?: throw ParseException(s, 0)
+
+ fun from(d: ReceiverParameterDescriptor): ClassReference =
+ when (val value = d.value) {
+ is ExtensionReceiver -> ClassReference(
+ classNames = value.type.constructorName.orEmpty(),
+ typeBounds = value.declarationDescriptor.typeParameters.map {
+ ClassReference(
+ it.fqNameSafe.toString(),
+ it.upperBounds.map { from(it) }
+ )
+ }
+ )
+ else -> ClassReference(d.value.type.constructorName.orEmpty())
+ }
+
+ private fun from(t: KotlinType): ClassReference =
+ ClassReference(t.constructorName.orEmpty(), t.arguments.map { from(it) })
+
+ private fun from(t: TypeProjection): ClassReference =
+ if (t.isStarProjection) {
+ starProjection
+ } else {
+ from(t.type)
+ }
+
+ private fun typeBoundsFrom(s: String) =
+ s.split(",").filter { it.isNotBlank() }.map { ClassReference.from(it) }
+
+ val starProjection = ClassReference("*")
+ }
}
private operator fun <T> List<T>.component6(): T = get(5)
diff --git a/core/src/main/kotlin/pages/PageBuilder.kt b/core/src/main/kotlin/pages/PageBuilder.kt
new file mode 100644
index 00000000..d20635a4
--- /dev/null
+++ b/core/src/main/kotlin/pages/PageBuilder.kt
@@ -0,0 +1,95 @@
+package org.jetbrains.dokka.pages
+
+import org.jetbrains.dokka.Model.*
+import org.jetbrains.dokka.Model.Function
+
+class DefaultPageBuilder(
+ override val rootContentGroup: RootContentBuilder
+) : PageBuilder {
+
+ override fun pageForModule(m: Module): ModulePageNode =
+ ModulePageNode("root", contentForModule(m), m, m.packages.map { pageForPackage(it) })
+
+ override fun pageForPackage(p: Package) =
+ PackagePageNode(p.name, contentForPackage(p), p.dri, p,
+ p.classes.map { pageForClass(it) } +
+ p.functions.map { pageForMember(it) } +
+ p.properties.map { pageForMember(it) })
+
+ override fun pageForClass(c: Class): ClassPageNode =
+ ClassPageNode(c.name, contentForClass(c), c.dri, c,
+ c.constructors.map { pageForMember(it) } +
+ c.classes.map { pageForClass(it) } +
+ c.functions.map { pageForMember(it) })
+
+ override fun pageForMember(m: CallableNode): MemberPageNode =
+ when (m) {
+ is Function ->
+ MemberPageNode(m.name, contentForFunction(m), m.dri, m)
+ else -> throw IllegalStateException("$m should not be present here")
+ }
+
+ private fun group(node: DocumentationNode, content: PageContentBuilderFunction) =
+ rootContentGroup(node, ContentKind.Main, content)
+
+ private fun contentForModule(m: Module) = group(m) {
+ header(1) { text("root") }
+ block("Packages", 2, ContentKind.Packages, m.packages, m.platformData) {
+ link(it.name, it.dri)
+ }
+ text("Index\n")
+ text("Link to allpage here")
+ }
+
+ private fun contentForPackage(p: Package) = group(p) {
+ header(1) { text("Package ${p.name}") }
+ block("Types", 2, ContentKind.Properties, p.classes, p.platformData) {
+ link(it.name, it.dri)
+ text(it.briefDocstring)
+ }
+ block("Functions", 2, ContentKind.Functions, p.functions, p.platformData) {
+ link(it.name, it.dri)
+ signature(it)
+ text(it.briefDocstring)
+ }
+ }
+
+ private fun contentForClass(c: Class) = group(c) {
+ header(1) { text(c.name) }
+ c.inherited.takeIf { it.isNotEmpty() }?.let {
+ header(2) { text("SuperInterfaces") }
+ linkTable(it)
+ }
+ c.commentsData.forEach { (doc, links) -> comment(doc, links) }
+ block("Constructors", 2, ContentKind.Functions, c.constructors, c.platformData) {
+ link(it.name, it.dri)
+ signature(it)
+ text(it.briefDocstring)
+ }
+ block("Functions", 2, ContentKind.Functions, c.functions, c.platformData) {
+ link(it.name, it.dri)
+ signature(it)
+ text(it.briefDocstring)
+ }
+ }
+
+ private fun contentForFunction(f: Function) = group(f) {
+ header(1) { text(f.name) }
+ signature(f)
+ f.commentsData.forEach { (doc, links) -> markdown(doc, links) }
+ block("Parameters", 2, ContentKind.Parameters, f.children, f.platformData) {
+ text(it.name ?: "<receiver>")
+ it.commentsData.forEach { (doc, links) -> markdown(doc, links) }
+ }
+ }
+}
+
+typealias RootContentBuilder = (DocumentationNode, Kind, PageContentBuilderFunction) -> ContentGroup
+
+interface PageBuilder {
+ val rootContentGroup: RootContentBuilder
+ fun pageForModule(m: Module): ModulePageNode
+ fun pageForPackage(p: Package): PackagePageNode
+ fun pageForMember(m: CallableNode): MemberPageNode
+ fun pageForClass(c: Class): ClassPageNode
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/pages/PageContentBuilder.kt b/core/src/main/kotlin/pages/PageContentBuilder.kt
new file mode 100644
index 00000000..9d07a098
--- /dev/null
+++ b/core/src/main/kotlin/pages/PageContentBuilder.kt
@@ -0,0 +1,206 @@
+package org.jetbrains.dokka.pages
+
+import org.jetbrains.dokka.DokkaLogger
+import org.jetbrains.dokka.Model.DocumentationNode
+import org.jetbrains.dokka.Model.Function
+import org.jetbrains.dokka.Model.Parameter
+import org.jetbrains.dokka.Model.TypeWrapper
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.parseMarkdown
+
+class DefaultPageContentBuilder(
+ private val node: DocumentationNode,
+ private val kind: Kind,
+ private val markdownConverter: MarkdownToContentConverter,
+ val logger: DokkaLogger,
+ private val styles: Set<Style> = emptySet(),
+ private val extras: Set<Extra> = emptySet()
+) : PageContentBuilder {
+ private val contents = mutableListOf<ContentNode>()
+
+ private fun createText(text: String) =
+ ContentText(text, DCI(node.dri, ContentKind.Symbol), node.platformData, styles, extras)
+
+ private fun build() = ContentGroup(
+ contents.toList(),
+ DCI(node.dri, kind),
+ node.platformData,
+ styles,
+ extras
+ )
+
+ override fun header(level: Int, block: PageContentBuilderFunction) {
+ contents += ContentHeader(level, group(ContentKind.Symbol, block))
+ }
+
+ override fun text(text: String) {
+ contents += createText(text)
+ }
+
+ private fun signature(f: Function, block: PageContentBuilderFunction) {
+ contents += group(f, ContentKind.Symbol, block)
+ }
+
+ override fun signature(f: Function) = signature(f) {
+ text("fun ")
+ if (f.receiver is Parameter) {
+ type(f.receiver.type)
+ text(".")
+ }
+ link(f.name, f.dri)
+ text("(")
+ list(f.parameters, "", "", ", ") {
+ link(it.name!!, it.dri)
+ text(": ")
+ type(it.type)
+ }
+ text(")")
+ val returnType = f.returnType
+ if (!f.isConstructor && returnType != null &&
+ returnType.constructorFqName != Unit::class.qualifiedName) {
+ text(": ")
+ type(returnType)
+ }
+ }
+
+ override fun linkTable(elements: List<DRI>) {
+ contents += ContentTable(
+ emptyList(),
+ elements.map { group(node, ContentKind.Classes) { link(it.classNames ?: "", it) } },
+ DCI(node.dri, kind),
+ node.platformData, styles, extras
+ )
+ }
+
+ override fun <T : DocumentationNode> block(
+ name: String,
+ level: Int,
+ kind: Kind,
+ elements: Iterable<T>,
+ platformData: Set<PlatformData>,
+ operation: PageContentBuilder.(T) -> Unit
+ ) {
+ header(level) { text(name) }
+
+ contents += ContentTable(
+ emptyList(),
+ elements.map { group(it, kind) { operation(it) } },
+ DCI(node.dri, kind),
+ platformData, styles, extras
+ )
+ }
+
+ override fun <T> list(
+ elements: List<T>,
+ prefix: String,
+ suffix: String,
+ separator: String,
+ operation: PageContentBuilder.(T) -> Unit
+ ) {
+ if (elements.isNotEmpty()) {
+ if (prefix.isNotEmpty()) text(prefix)
+ elements.dropLast(1).forEach {
+ operation(it)
+ text(separator)
+ }
+ operation(elements.last())
+ if (suffix.isNotEmpty()) text(suffix)
+ }
+ }
+
+ override fun link(text: String, address: DRI) {
+ contents += ContentDRILink(
+ listOf(createText(text)),
+ address,
+ DCI(node.dri, ContentKind.Symbol),
+ node.platformData
+ )
+ }
+
+ override fun comment(raw: String, links: Map<String, DRI>) {
+ contents += group(ContentKind.Comment) {
+ with(this as DefaultPageContentBuilder) {
+ contents += markdownConverter.buildContent(
+ parseMarkdown(raw),
+ DCI(node.dri, ContentKind.Comment),
+ node.platformData,
+ links
+ )
+ }
+ }
+ }
+
+ override fun markdown(raw: String, links: Map<String, DRI>) {
+ contents += markdownConverter.buildContent(
+ parseMarkdown(raw), DCI(node.dri, ContentKind.Sample),
+ node.platformData,
+ links
+ )
+ }
+
+ private fun group(kind: Kind, block: PageContentBuilderFunction): ContentGroup =
+ group(node, kind, block)
+
+ override fun group(
+ node: DocumentationNode,
+ kind: Kind,
+ block: PageContentBuilderFunction
+ ): ContentGroup = group(node, kind, markdownConverter, logger, block)
+
+ companion object {
+ fun group(
+ node: DocumentationNode,
+ kind: Kind,
+ markdownConverter: MarkdownToContentConverter,
+ logger: DokkaLogger,
+ block: PageContentBuilderFunction
+ ): ContentGroup =
+ DefaultPageContentBuilder(node, kind, markdownConverter, logger).apply(block).build()
+ }
+}
+
+
+private fun PageContentBuilder.type(t: TypeWrapper) {
+ if (t.constructorNamePathSegments.isNotEmpty() && t.dri != null)
+ link(t.constructorNamePathSegments.last(), t.dri!!)
+ else (this as? DefaultPageContentBuilder)?.let {
+ logger.error("type $t cannot be resolved")
+ text("???")
+ }
+ list(t.arguments, prefix = "<", suffix = ">", separator = ", ") {
+ type(it)
+ }
+}
+
+typealias PageContentBuilderFunction = PageContentBuilder.() -> Unit
+
+@DslMarker
+annotation class ContentMarker
+
+@ContentMarker
+interface PageContentBuilder {
+ fun group(node: DocumentationNode, kind: Kind, block: PageContentBuilderFunction): ContentGroup
+ fun text(text: String)
+ fun signature(f: Function)
+ fun link(text: String, address: DRI)
+ fun linkTable(elements: List<DRI>)
+ fun comment(raw: String, links: Map<String, DRI>)
+ fun markdown(raw: String, links: Map<String, DRI>)
+ fun header(level: Int, block: PageContentBuilder.() -> Unit)
+ fun <T> list(
+ elements: List<T>,
+ prefix: String,
+ suffix: String,
+ separator: String,
+ operation: PageContentBuilder.(T) -> Unit
+ )
+
+ fun <T : DocumentationNode> block(
+ name: String,
+ level: Int,
+ kind: Kind,
+ elements: Iterable<T>,
+ platformData: Set<PlatformData>,
+ operation: PageContentBuilder.(T) -> Unit
+ )
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/pages/PageNodes.kt b/core/src/main/kotlin/pages/PageNodes.kt
index 6c56d5fe..a07aa8c1 100644
--- a/core/src/main/kotlin/pages/PageNodes.kt
+++ b/core/src/main/kotlin/pages/PageNodes.kt
@@ -6,10 +6,10 @@ import org.jetbrains.dokka.links.DRI
interface PageNode {
val name: String
- var content: ContentNode
+ val content: ContentNode
val parent: PageNode?
val dri: DRI
- val documentationNode: DocumentationNode<*>?
+ val documentationNode: DocumentationNode?
val embeddedResources: List<String>
val children: List<PageNode>
@@ -42,8 +42,8 @@ abstract class BasicPageNode(children: List<PageNode>) : PageNode {
class ModulePageNode(
override val name: String,
- override var content: ContentNode,
- override val documentationNode: DocumentationNode<*>?,
+ override val content: ContentNode,
+ override val documentationNode: DocumentationNode?,
children: List<PageNode>,
override val embeddedResources: List<String> = listOf()
) : BasicPageNode(children) {
@@ -62,9 +62,9 @@ class ModulePageNode(
class PackagePageNode(
override val name: String,
- override var content: ContentNode,
+ override val content: ContentNode,
override val dri: DRI,
- override val documentationNode: DocumentationNode<*>?,
+ override val documentationNode: DocumentationNode?,
children: List<PageNode>,
override val embeddedResources: List<String> = listOf()
) : BasicPageNode(children) {
@@ -81,9 +81,9 @@ class PackagePageNode(
class ClassPageNode(
override val name: String,
- override var content: ContentNode,
+ override val content: ContentNode,
override val dri: DRI,
- override val documentationNode: DocumentationNode<*>?,
+ override val documentationNode: DocumentationNode?,
children: List<PageNode>,
override val embeddedResources: List<String> = listOf()
) : BasicPageNode(children) {
@@ -100,9 +100,9 @@ class ClassPageNode(
class MemberPageNode(
override val name: String,
- override var content: ContentNode,
+ override val content: ContentNode,
override val dri: DRI,
- override val documentationNode: DocumentationNode<*>?,
+ override val documentationNode: DocumentationNode?,
children: List<PageNode> = emptyList(),
override val embeddedResources: List<String> = listOf()
) : BasicPageNode(children) {
diff --git a/core/src/main/kotlin/transformers/DefaultDocumentationToPageTransformer.kt b/core/src/main/kotlin/transformers/DefaultDocumentationToPageTransformer.kt
index 23f3443c..4c44ecdf 100644
--- a/core/src/main/kotlin/transformers/DefaultDocumentationToPageTransformer.kt
+++ b/core/src/main/kotlin/transformers/DefaultDocumentationToPageTransformer.kt
@@ -1,15 +1,11 @@
package org.jetbrains.dokka.transformers
import org.jetbrains.dokka.DokkaLogger
-import org.jetbrains.dokka.Model.*
-import org.jetbrains.dokka.Model.Function
-import org.jetbrains.dokka.links.DRI
-import org.jetbrains.dokka.pages.*
-import org.jetbrains.dokka.pages.Extra
-import org.jetbrains.dokka.parseMarkdown
-import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
-import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
-import org.jetbrains.kotlin.types.KotlinType
+import org.jetbrains.dokka.Model.Module
+import org.jetbrains.dokka.pages.DefaultPageBuilder
+import org.jetbrains.dokka.pages.DefaultPageContentBuilder
+import org.jetbrains.dokka.pages.MarkdownToContentConverter
+import org.jetbrains.dokka.pages.ModulePageNode
class DefaultDocumentationToPageTransformer(
@@ -17,216 +13,8 @@ class DefaultDocumentationToPageTransformer(
private val logger: DokkaLogger
) : DocumentationToPageTransformer {
override fun transform(module: Module): ModulePageNode =
- PageBuilder().pageForModule(module)
+ DefaultPageBuilder { node, kind, operation ->
+ DefaultPageContentBuilder.group(node, kind, markdownConverter, logger, operation)
+ }.pageForModule(module)
- private inner class PageBuilder {
- fun pageForModule(m: Module): ModulePageNode =
- ModulePageNode("root", contentForModule(m), m, m.packages.map { pageForPackage(it) })
-
- private fun pageForPackage(p: Package) =
- PackagePageNode(p.name, contentForPackage(p), p.dri, p,
- p.classes.map { pageForClass(it) } +
- p.functions.map { pageForMember(it) } +
- p.properties.map { pageForMember(it) })
-
- private fun pageForClass(c: Class): ClassPageNode =
- ClassPageNode(c.name, contentForClass(c), c.dri, c,
- c.constructors.map { pageForMember(it) } +
- c.classes.map { pageForClass(it) } +
- c.functions.map { pageForMember(it) })
-
- private fun pageForMember(m: CallableNode<*>): MemberPageNode =
- when (m) {
- is Function ->
- MemberPageNode(m.name, contentForFunction(m), m.dri, m)
- else -> throw IllegalStateException("$m should not be present here")
- }
-
- private fun contentForModule(m: Module) = group(m) {
- header(1) { text("root") }
- block("Packages", 2, ContentKind.Packages, m.packages, m.platformData) {
- link(it.name, it.dri)
- }
- text("Index\n")
- text("Link to allpage here")
- }
-
- private fun contentForPackage(p: Package) = group(p) {
- header(1) { text("Package ${p.name}") }
- block("Types", 2, ContentKind.Properties, p.classes, p.platformData) {
- link(it.name, it.dri)
- text(it.briefDocstring)
- }
- block("Functions", 2, ContentKind.Functions, p.functions, p.platformData) {
- link(it.name, it.dri)
- signature(it)
- text(it.briefDocstring)
- }
- }
-
- private fun contentForClass(c: Class) = group(c) {
- header(1) { text(c.name) }
- c.commentsData.forEach { (doc, links) -> comment(doc, links) }
- block("Constructors", 2, ContentKind.Functions, c.constructors, c.platformData) {
- link(it.name, it.dri)
- signature(it)
- text(it.briefDocstring)
- }
- block("Functions", 2, ContentKind.Functions, c.functions, c.platformData) {
- link(it.name, it.dri)
- signature(it)
- text(it.briefDocstring)
- }
- }
-
- private fun contentForFunction(f: Function) = group(f) {
- header(1) { text(f.name) }
- signature(f)
- f.commentsData.forEach { (doc, links) -> markdown(doc, links) }
- block("Parameters", 2, ContentKind.Parameters, f.children, f.platformData) {
- text(it.name ?: "<receiver>")
- it.commentsData.forEach { (doc, links) -> markdown(doc, links) }
- }
- }
- }
-
- // TODO: Make some public builder or merge it with page builder, whateva
- private inner class ContentBuilder(
- val node: DocumentationNode<*>,
- val kind: Kind,
- val styles: Set<Style> = emptySet(),
- val extras: Set<Extra> = emptySet()
- ) {
- private val contents = mutableListOf<ContentNode>()
-
- fun build() = ContentGroup(
- contents.toList(),
- DCI(node.dri, kind),
- node.platformData,
- styles,
- extras
- )
-
- fun header(level: Int, block: ContentBuilder.() -> Unit) {
- contents += ContentHeader(level, group(ContentKind.Symbol, block))
- }
-
- private fun createText(text: String) =
- ContentText(text, DCI(node.dri, ContentKind.Symbol), node.platformData, styles, extras)
-
- fun text(text: String) {
- contents += createText(text)
- }
-
- inline fun signature(f: Function, block: ContentBuilder.() -> Unit) {
- contents += group(f, ContentKind.Symbol, block)
- }
-
- inline fun <T : DocumentationNode<*>> block(
- name: String,
- level: Int,
- kind: Kind,
- elements: Iterable<T>,
- platformData: Set<PlatformData>,
- operation: ContentBuilder.(T) -> Unit
- ) {
- header(level) { text(name) }
-
- contents += ContentTable(
- emptyList(),
- elements.map { group(it, kind) { operation(it) } },
- DCI(node.dri, kind),
- platformData, styles, extras
- )
- }
-
- inline fun <T> list(
- elements: List<T>,
- prefix: String = "",
- suffix: String = "",
- separator: String = ", ",
- block: ContentBuilder.(T) -> Unit
- ) {
- if (elements.isNotEmpty()) {
- if (prefix.isNotEmpty()) text(prefix)
- elements.dropLast(1).forEach {
- block(it)
- text(separator)
- }
- block(elements.last())
- if (suffix.isNotEmpty()) text(suffix)
- }
- }
-
- fun link(text: String, address: DRI) {
- contents += ContentDRILink(
- listOf(createText(text)),
- address,
- DCI(node.dri, ContentKind.Symbol),
- node.platformData
- )
- }
-
- fun comment(raw: String, links: Map<String, DRI>) {
- contents += group(ContentKind.Comment) {
- contents += markdownConverter.buildContent(
- parseMarkdown(raw),
- DCI(node.dri, ContentKind.Comment),
- node.platformData,
- links
- )
- }
- }
-
- fun markdown(raw: String, links: Map<String, DRI>) {
- contents += markdownConverter.buildContent(
- parseMarkdown(raw), DCI(node.dri, ContentKind.Sample),
- node.platformData,
- links
- )
- }
-
- private inline fun group(kind: Kind, block: ContentBuilder.() -> Unit): ContentGroup =
- group(node, kind, block)
- }
-
- private inline fun group(
- node: DocumentationNode<*>,
- kind: Kind = ContentKind.Main,
- block: ContentBuilder.() -> Unit
- ) = ContentBuilder(node, kind).apply(block).build()
-
- // When builder is made public it will be moved as extension method to someplace near Function model
- private fun ContentBuilder.signature(f: Function) = signature(f) { // TODO: wrap this in ContentCode
- text("fun ")
- if (f.receiver is Parameter) {
- type(f.receiver.descriptors.first().descriptor.type)
- text(".")
- }
- link(f.name, f.dri)
- text("(")
- list(f.parameters) {
- link(it.name!!, it.dri)
- text(": ")
- type(it.descriptors.first().descriptor.type)
- }
- text(")")
- val returnType = f.descriptors.first().descriptor.returnType
- if (f.descriptors.first().descriptor !is ConstructorDescriptor && returnType != null &&
- returnType.constructor.declarationDescriptor?.fqNameSafe?.asString() != Unit::class.qualifiedName) {
- text(": ")
- type(returnType)
- }
- }
-
- private fun ContentBuilder.type(t: KotlinType) {
- t.constructor.declarationDescriptor?.also { link(it.fqNameSafe.pathSegments().last().asString(), DRI.from(it)) }
- ?: run {
- logger.error("type $t cannot be resolved")
- text("???")
- }
- list(t.arguments, prefix = "<", suffix = ">") {
- type(it.type)
- }
- }
} \ No newline at end of file