diff options
-rw-r--r-- | core/src/main/kotlin/DokkaDescriptorVisitor.kt | 90 | ||||
-rw-r--r-- | core/src/main/kotlin/Model/DocumentationNode.kt | 101 | ||||
-rw-r--r-- | core/src/main/kotlin/Model/transformers/DocumentationNodesMerger.kt | 38 | ||||
-rw-r--r-- | core/src/main/kotlin/Utilities/nodeDebug.kt | 2 | ||||
-rw-r--r-- | core/src/main/kotlin/links/DRI.kt | 72 | ||||
-rw-r--r-- | core/src/main/kotlin/pages/PageBuilder.kt | 95 | ||||
-rw-r--r-- | core/src/main/kotlin/pages/PageContentBuilder.kt | 206 | ||||
-rw-r--r-- | core/src/main/kotlin/pages/PageNodes.kt | 20 | ||||
-rw-r--r-- | core/src/main/kotlin/transformers/DefaultDocumentationToPageTransformer.kt | 228 |
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 |