package org.jetbrains.dokka import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult import java.util.* enum class NodeKind { Unknown, Package, Class, Interface, Enum, AnnotationClass, Exception, EnumItem, Object, TypeAlias, Constructor, Function, Property, Field, CompanionObjectProperty, CompanionObjectFunction, Parameter, Receiver, TypeParameter, Type, Supertype, UpperBound, LowerBound, TypeAliasUnderlyingType, Modifier, NullabilityModifier, Module, ExternalClass, Annotation, Value, SourceUrl, SourcePosition, Signature, ExternalLink, QualifiedName, Platform, AllTypes, /** * A note which is rendered once on a page documenting a group of overloaded functions. * Needs to be generated equally on all overloads. */ OverloadGroupNote, GroupNode; companion object { val classLike = setOf(Class, Interface, Enum, AnnotationClass, Exception, Object, TypeAlias) val memberLike = setOf(Function, Property, Constructor, CompanionObjectFunction, CompanionObjectProperty, EnumItem) } } open class DocumentationNode(val name: String, content: Content, val kind: NodeKind) { private val references = LinkedHashSet() var content: Content = content private set val summary: ContentNode get() = content.summary val owner: DocumentationNode? get() = references(RefKind.Owner).singleOrNull()?.to val details: List get() = references(RefKind.Detail).map { it.to } val members: List get() = references(RefKind.Member).map { it.to } val inheritedMembers: List get() = references(RefKind.InheritedMember).map { it.to } val inheritedCompanionObjectMembers: List get() = references(RefKind.InheritedCompanionObjectMember).map { it.to } val extensions: List get() = references(RefKind.Extension).map { it.to } val inheritors: List get() = references(RefKind.Inheritor).map { it.to } val overrides: List get() = references(RefKind.Override).map { it.to } val links: List get() = references(RefKind.Link).map { it.to } val hiddenLinks: List get() = references(RefKind.HiddenLink).map { it.to } val annotations: List get() = references(RefKind.Annotation).map { it.to } val deprecation: DocumentationNode? get() = references(RefKind.Deprecation).singleOrNull()?.to val platforms: List get() = references(RefKind.Platform).map { it.to.name } val supertypes: List get() = details(NodeKind.Supertype) val superclass: DocumentationNode? get() = supertypes.firstNotNullResult { it.links.firstOrNull { it.kind in NodeKind.classLike } } // TODO: Should we allow node mutation? Model merge will copy by ref, so references are transparent, which could nice fun addReferenceTo(to: DocumentationNode, kind: RefKind) { references.add(DocumentationReference(this, to, kind)) } fun dropReferences(predicate: (DocumentationReference) -> Boolean) { references.removeAll(predicate) } fun addAllReferencesFrom(other: DocumentationNode) { references.addAll(other.references) } fun updateContent(body: MutableContent.() -> Unit) { if (content !is MutableContent) { content = MutableContent() } (content as MutableContent).body() } fun details(kind: NodeKind): List = details.filter { it.kind == kind } fun members(kind: NodeKind): List = members.filter { it.kind == kind } fun inheritedMembers(kind: NodeKind): List = inheritedMembers.filter { it.kind == kind } fun inheritedCompanionObjectMembers(kind: NodeKind): List = inheritedCompanionObjectMembers.filter { it.kind == kind } fun links(kind: NodeKind): List = links.filter { it.kind == kind } fun detail(kind: NodeKind): DocumentationNode = details.filter { it.kind == kind }.single() fun detailOrNull(kind: NodeKind): DocumentationNode? = details.filter { it.kind == kind }.singleOrNull() fun member(kind: NodeKind): DocumentationNode = members.filter { it.kind == kind }.single() fun link(kind: NodeKind): DocumentationNode = links.filter { it.kind == kind }.single() fun references(kind: RefKind): List = references.filter { it.kind == kind } fun allReferences(): Set = references override fun toString(): String { return "$kind:$name" } } class DocumentationModule(name: String, content: Content = Content.Empty) : DocumentationNode(name, content, NodeKind.Module) { val nodeRefGraph = NodeReferenceGraph() } val DocumentationNode.path: List get() { val parent = owner ?: return listOf(this) return parent.path + this } fun DocumentationNode.findOrCreatePackageNode(packageName: String, packageContent: Map, refGraph: NodeReferenceGraph): DocumentationNode { val existingNode = members(NodeKind.Package).firstOrNull { it.name == packageName } if (existingNode != null) { return existingNode } val newNode = DocumentationNode(packageName, packageContent.getOrElse(packageName) { Content.Empty }, NodeKind.Package) append(newNode, RefKind.Member) refGraph.register(packageName, newNode) return newNode } fun DocumentationNode.append(child: DocumentationNode, kind: RefKind) { addReferenceTo(child, kind) when (kind) { RefKind.Detail -> child.addReferenceTo(this, RefKind.Owner) RefKind.Member -> child.addReferenceTo(this, RefKind.Owner) RefKind.Owner -> child.addReferenceTo(this, RefKind.Member) else -> { /* Do not add any links back for other types */ } } } fun DocumentationNode.appendTextNode(text: String, kind: NodeKind, refKind: RefKind = RefKind.Detail) { append(DocumentationNode(text, Content.Empty, kind), refKind) } fun DocumentationNode.qualifiedName(): String { if (kind == NodeKind.Type) { return qualifiedNameFromType() } return path.drop(1).map { it.name }.filter { it.length > 0 }.joinToString(".") } fun DocumentationNode.simpleName() = name.substringAfterLast('.')