aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/kotlin/Model
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/main/kotlin/Model')
-rw-r--r--core/src/main/kotlin/Model/Content.kt231
-rw-r--r--core/src/main/kotlin/Model/DocumentationNode.kt162
-rw-r--r--core/src/main/kotlin/Model/DocumentationReference.kt61
-rw-r--r--core/src/main/kotlin/Model/PackageDocs.kt60
-rw-r--r--core/src/main/kotlin/Model/SourceLinks.kt56
5 files changed, 570 insertions, 0 deletions
diff --git a/core/src/main/kotlin/Model/Content.kt b/core/src/main/kotlin/Model/Content.kt
new file mode 100644
index 00000000..6556b09e
--- /dev/null
+++ b/core/src/main/kotlin/Model/Content.kt
@@ -0,0 +1,231 @@
+package org.jetbrains.dokka
+
+public interface ContentNode {
+ val textLength: Int
+}
+
+public object ContentEmpty : ContentNode {
+ override val textLength: Int get() = 0
+}
+
+public open class ContentBlock() : ContentNode {
+ val children = arrayListOf<ContentNode>()
+
+ fun append(node: ContentNode) {
+ children.add(node)
+ }
+
+ fun isEmpty() = children.isEmpty()
+
+ override fun equals(other: Any?): Boolean =
+ other is ContentBlock && javaClass == other.javaClass && children == other.children
+
+ override fun hashCode(): Int =
+ children.hashCode()
+
+ override val textLength: Int
+ get() = children.sumBy { it.textLength }
+}
+
+enum class IdentifierKind {
+ TypeName,
+ ParameterName,
+ AnnotationName,
+ SummarizedTypeName,
+ Other
+}
+
+public data class ContentText(val text: String) : ContentNode {
+ override val textLength: Int
+ get() = text.length
+}
+
+public data class ContentKeyword(val text: String) : ContentNode {
+ override val textLength: Int
+ get() = text.length
+}
+
+public data class ContentIdentifier(val text: String, val kind: IdentifierKind = IdentifierKind.Other) : ContentNode {
+ override val textLength: Int
+ get() = text.length
+}
+
+public data class ContentSymbol(val text: String) : ContentNode {
+ override val textLength: Int
+ get() = text.length
+}
+
+public data class ContentEntity(val text: String) : ContentNode {
+ override val textLength: Int
+ get() = text.length
+}
+
+public object ContentNonBreakingSpace: ContentNode {
+ override val textLength: Int
+ get() = 1
+}
+
+public object ContentSoftLineBreak: ContentNode {
+ override val textLength: Int
+ get() = 0
+}
+
+public object ContentIndentedSoftLineBreak: ContentNode {
+ override val textLength: Int
+ get() = 0
+}
+
+public class ContentParagraph() : ContentBlock()
+public class ContentEmphasis() : ContentBlock()
+public class ContentStrong() : ContentBlock()
+public class ContentStrikethrough() : ContentBlock()
+public class ContentCode() : ContentBlock()
+public class ContentBlockCode(val language: String = "") : ContentBlock()
+
+public abstract class ContentNodeLink() : ContentBlock() {
+ abstract val node: DocumentationNode?
+}
+
+public class ContentNodeDirectLink(override val node: DocumentationNode): ContentNodeLink() {
+ override fun equals(other: Any?): Boolean =
+ super.equals(other) && other is ContentNodeDirectLink && node.name == other.node.name
+
+ override fun hashCode(): Int =
+ children.hashCode() * 31 + node.name.hashCode()
+}
+
+public class ContentNodeLazyLink(val linkText: String, val lazyNode: () -> DocumentationNode?): ContentNodeLink() {
+ override val node: DocumentationNode? get() = lazyNode()
+
+ override fun equals(other: Any?): Boolean =
+ super.equals(other) && other is ContentNodeLazyLink && linkText == other.linkText
+
+ override fun hashCode(): Int =
+ children.hashCode() * 31 + linkText.hashCode()
+}
+
+public class ContentExternalLink(val href : String) : ContentBlock() {
+ override fun equals(other: Any?): Boolean =
+ super.equals(other) && other is ContentExternalLink && href == other.href
+
+ override fun hashCode(): Int =
+ children.hashCode() * 31 + href.hashCode()
+}
+
+public class ContentUnorderedList() : ContentBlock()
+public class ContentOrderedList() : ContentBlock()
+public class ContentListItem() : ContentBlock()
+
+public class ContentHeading(val level: Int) : ContentBlock()
+
+public class ContentSection(public val tag: String, public val subjectName: String?) : ContentBlock() {
+ override fun equals(other: Any?): Boolean =
+ super.equals(other) && other is ContentSection && tag == other.tag && subjectName == other.subjectName
+
+ override fun hashCode(): Int =
+ children.hashCode() * 31 * 31 + tag.hashCode() * 31 + (subjectName?.hashCode() ?: 0)
+}
+
+public object ContentTags {
+ val Description = "Description"
+ val SeeAlso = "See Also"
+}
+
+fun content(body: ContentBlock.() -> Unit): ContentBlock {
+ val block = ContentBlock()
+ block.body()
+ return block
+}
+
+fun ContentBlock.text(value: String) = append(ContentText(value))
+fun ContentBlock.keyword(value: String) = append(ContentKeyword(value))
+fun ContentBlock.symbol(value: String) = append(ContentSymbol(value))
+fun ContentBlock.identifier(value: String, kind: IdentifierKind = IdentifierKind.Other) = append(ContentIdentifier(value, kind))
+fun ContentBlock.nbsp() = append(ContentNonBreakingSpace)
+fun ContentBlock.softLineBreak() = append(ContentSoftLineBreak)
+fun ContentBlock.indentedSoftLineBreak() = append(ContentIndentedSoftLineBreak)
+
+fun ContentBlock.strong(body: ContentBlock.() -> Unit) {
+ val strong = ContentStrong()
+ strong.body()
+ append(strong)
+}
+
+fun ContentBlock.code(body: ContentBlock.() -> Unit) {
+ val code = ContentCode()
+ code.body()
+ append(code)
+}
+
+fun ContentBlock.link(to: DocumentationNode, body: ContentBlock.() -> Unit) {
+ val block = ContentNodeDirectLink(to)
+ block.body()
+ append(block)
+}
+
+public open class Content(): ContentBlock() {
+ public open val sections: List<ContentSection> get() = emptyList()
+ public open val summary: ContentNode get() = ContentEmpty
+ public open val description: ContentNode get() = ContentEmpty
+
+ fun findSectionByTag(tag: String): ContentSection? =
+ sections.firstOrNull { tag.equals(it.tag, ignoreCase = true) }
+
+ companion object {
+ val Empty = Content()
+
+ fun of(vararg child: ContentNode): Content {
+ val result = MutableContent()
+ child.forEach { result.append(it) }
+ return result
+ }
+ }
+}
+
+public open class MutableContent() : Content() {
+ private val sectionList = arrayListOf<ContentSection>()
+ public override val sections: List<ContentSection>
+ get() = sectionList
+
+ fun addSection(tag: String?, subjectName: String?): ContentSection {
+ val section = ContentSection(tag ?: "", subjectName)
+ sectionList.add(section)
+ return section
+ }
+
+ public override val summary: ContentNode get() = children.firstOrNull() ?: ContentEmpty
+
+ public override val description: ContentNode by lazy {
+ val descriptionNodes = children.drop(1)
+ if (descriptionNodes.isEmpty()) {
+ ContentEmpty
+ } else {
+ val result = ContentSection(ContentTags.Description, null)
+ result.children.addAll(descriptionNodes)
+ result
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other !is Content)
+ return false
+ return sections == other.sections && children == other.children
+ }
+
+ override fun hashCode(): Int {
+ return sections.map { it.hashCode() }.sum()
+ }
+
+ override fun toString(): String {
+ if (sections.isEmpty())
+ return "<empty>"
+ return (listOf(summary, description) + sections).joinToString()
+ }
+}
+
+fun javadocSectionDisplayName(sectionName: String?): String? =
+ when(sectionName) {
+ "param" -> "Parameters"
+ "throws", "exception" -> "Exceptions"
+ else -> sectionName?.capitalize()
+ }
diff --git a/core/src/main/kotlin/Model/DocumentationNode.kt b/core/src/main/kotlin/Model/DocumentationNode.kt
new file mode 100644
index 00000000..52881f65
--- /dev/null
+++ b/core/src/main/kotlin/Model/DocumentationNode.kt
@@ -0,0 +1,162 @@
+package org.jetbrains.dokka
+
+import java.util.*
+
+public open class DocumentationNode(val name: String,
+ content: Content,
+ val kind: DocumentationNode.Kind) {
+
+ private val references = LinkedHashSet<DocumentationReference>()
+
+ var content: Content = content
+ private set
+
+ public val summary: ContentNode get() = content.summary
+
+ public val owner: DocumentationNode?
+ get() = references(DocumentationReference.Kind.Owner).singleOrNull()?.to
+ public val details: List<DocumentationNode>
+ get() = references(DocumentationReference.Kind.Detail).map { it.to }
+ public val members: List<DocumentationNode>
+ get() = references(DocumentationReference.Kind.Member).map { it.to }
+ public val inheritedMembers: List<DocumentationNode>
+ get() = references(DocumentationReference.Kind.InheritedMember).map { it.to }
+ public val extensions: List<DocumentationNode>
+ get() = references(DocumentationReference.Kind.Extension).map { it.to }
+ public val inheritors: List<DocumentationNode>
+ get() = references(DocumentationReference.Kind.Inheritor).map { it.to }
+ public val overrides: List<DocumentationNode>
+ get() = references(DocumentationReference.Kind.Override).map { it.to }
+ public val links: List<DocumentationNode>
+ get() = references(DocumentationReference.Kind.Link).map { it.to }
+ public val hiddenLinks: List<DocumentationNode>
+ get() = references(DocumentationReference.Kind.HiddenLink).map { it.to }
+ public val annotations: List<DocumentationNode>
+ get() = references(DocumentationReference.Kind.Annotation).map { it.to }
+ public val deprecation: DocumentationNode?
+ get() = references(DocumentationReference.Kind.Deprecation).singleOrNull()?.to
+
+ // TODO: Should we allow node mutation? Model merge will copy by ref, so references are transparent, which could nice
+ public fun addReferenceTo(to: DocumentationNode, kind: DocumentationReference.Kind) {
+ references.add(DocumentationReference(this, to, kind))
+ }
+
+ public fun addAllReferencesFrom(other: DocumentationNode) {
+ references.addAll(other.references)
+ }
+
+ public fun updateContent(body: MutableContent.() -> Unit) {
+ if (content !is MutableContent) {
+ content = MutableContent()
+ }
+ (content as MutableContent).body()
+ }
+
+ public fun details(kind: DocumentationNode.Kind): List<DocumentationNode> = details.filter { it.kind == kind }
+ public fun members(kind: DocumentationNode.Kind): List<DocumentationNode> = members.filter { it.kind == kind }
+ public fun inheritedMembers(kind: DocumentationNode.Kind): List<DocumentationNode> = inheritedMembers.filter { it.kind == kind }
+ public fun links(kind: DocumentationNode.Kind): List<DocumentationNode> = links.filter { it.kind == kind }
+
+ public fun detail(kind: DocumentationNode.Kind): DocumentationNode = details.filter { it.kind == kind }.single()
+ public fun member(kind: DocumentationNode.Kind): DocumentationNode = members.filter { it.kind == kind }.single()
+ public fun link(kind: DocumentationNode.Kind): DocumentationNode = links.filter { it.kind == kind }.single()
+
+ public fun references(kind: DocumentationReference.Kind): List<DocumentationReference> = references.filter { it.kind == kind }
+ public fun allReferences(): Set<DocumentationReference> = references
+
+ public override fun toString(): String {
+ return "$kind:$name"
+ }
+
+ public enum class Kind {
+ Unknown,
+
+ Package,
+ Class,
+ Interface,
+ Enum,
+ AnnotationClass,
+ EnumItem,
+ Object,
+
+ Constructor,
+ Function,
+ Property,
+ Field,
+
+ CompanionObjectProperty,
+ CompanionObjectFunction,
+
+ Parameter,
+ Receiver,
+ TypeParameter,
+ Type,
+ Supertype,
+ UpperBound,
+ LowerBound,
+ Exception,
+
+ Modifier,
+ NullabilityModifier,
+
+ Module,
+
+ ExternalClass,
+ Annotation,
+
+ Value,
+
+ SourceUrl,
+ SourcePosition,
+
+ /**
+ * A note which is rendered once on a page documenting a group of overloaded functions.
+ * Needs to be generated equally on all overloads.
+ */
+ OverloadGroupNote;
+
+ companion object {
+ val classLike = setOf(Class, Interface, Enum, AnnotationClass, Object)
+ }
+ }
+}
+
+public class DocumentationModule(name: String, content: Content = Content.Empty)
+ : DocumentationNode(name, content, DocumentationNode.Kind.Module) {
+}
+
+val DocumentationNode.path: List<DocumentationNode>
+ get() {
+ val parent = owner ?: return listOf(this)
+ return parent.path + this
+ }
+
+fun DocumentationNode.findOrCreatePackageNode(packageName: String, packageContent: Map<String, Content>): DocumentationNode {
+ val existingNode = members(DocumentationNode.Kind.Package).firstOrNull { it.name == packageName }
+ if (existingNode != null) {
+ return existingNode
+ }
+ val newNode = DocumentationNode(packageName,
+ packageContent.getOrElse(packageName) { Content.Empty },
+ DocumentationNode.Kind.Package)
+ append(newNode, DocumentationReference.Kind.Member)
+ return newNode
+}
+
+fun DocumentationNode.append(child: DocumentationNode, kind: DocumentationReference.Kind) {
+ addReferenceTo(child, kind)
+ when (kind) {
+ DocumentationReference.Kind.Detail -> child.addReferenceTo(this, DocumentationReference.Kind.Owner)
+ DocumentationReference.Kind.Member -> child.addReferenceTo(this, DocumentationReference.Kind.Owner)
+ DocumentationReference.Kind.Owner -> child.addReferenceTo(this, DocumentationReference.Kind.Member)
+ else -> { /* Do not add any links back for other types */ }
+ }
+}
+
+fun DocumentationNode.appendTextNode(text: String,
+ kind: DocumentationNode.Kind,
+ refKind: DocumentationReference.Kind = DocumentationReference.Kind.Detail) {
+ append(DocumentationNode(text, Content.Empty, kind), refKind)
+}
+
+fun DocumentationNode.qualifiedName() = path.drop(1).map { it.name }.filter { it.length > 0 }.joinToString(".")
diff --git a/core/src/main/kotlin/Model/DocumentationReference.kt b/core/src/main/kotlin/Model/DocumentationReference.kt
new file mode 100644
index 00000000..898c92d7
--- /dev/null
+++ b/core/src/main/kotlin/Model/DocumentationReference.kt
@@ -0,0 +1,61 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Singleton
+
+public data class DocumentationReference(val from: DocumentationNode, val to: DocumentationNode, val kind: DocumentationReference.Kind) {
+ public enum class Kind {
+ Owner,
+ Member,
+ InheritedMember,
+ Detail,
+ Link,
+ HiddenLink,
+ Extension,
+ Inheritor,
+ Superclass,
+ Override,
+ Annotation,
+ Deprecation,
+ TopLevelPage
+ }
+}
+
+class PendingDocumentationReference(val lazyNodeFrom: () -> DocumentationNode?,
+ val lazyNodeTo: () -> DocumentationNode?,
+ val kind: DocumentationReference.Kind) {
+ fun resolve() {
+ val fromNode = lazyNodeFrom()
+ val toNode = lazyNodeTo()
+ if (fromNode != null && toNode != null) {
+ fromNode.addReferenceTo(toNode, kind)
+ }
+ }
+}
+
+@Singleton
+class NodeReferenceGraph() {
+ private val nodeMap = hashMapOf<String, DocumentationNode>()
+ val references = arrayListOf<PendingDocumentationReference>()
+
+ fun register(signature: String, node: DocumentationNode) {
+ nodeMap.put(signature, node)
+ }
+
+ fun link(fromNode: DocumentationNode, toSignature: String, kind: DocumentationReference.Kind) {
+ references.add(PendingDocumentationReference({ -> fromNode}, { -> nodeMap[toSignature]}, kind))
+ }
+
+ fun link(fromSignature: String, toNode: DocumentationNode, kind: DocumentationReference.Kind) {
+ references.add(PendingDocumentationReference({ -> nodeMap[fromSignature]}, { -> toNode}, kind))
+ }
+
+ fun link(fromSignature: String, toSignature: String, kind: DocumentationReference.Kind) {
+ references.add(PendingDocumentationReference({ -> nodeMap[fromSignature]}, { -> nodeMap[toSignature]}, kind))
+ }
+
+ fun lookup(signature: String): DocumentationNode? = nodeMap[signature]
+
+ fun resolveReferences() {
+ references.forEach { it.resolve() }
+ }
+}
diff --git a/core/src/main/kotlin/Model/PackageDocs.kt b/core/src/main/kotlin/Model/PackageDocs.kt
new file mode 100644
index 00000000..044c73d8
--- /dev/null
+++ b/core/src/main/kotlin/Model/PackageDocs.kt
@@ -0,0 +1,60 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Inject
+import com.google.inject.Singleton
+import org.intellij.markdown.MarkdownElementTypes
+import org.intellij.markdown.MarkdownTokenTypes
+import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyPackageDescriptor
+import java.io.File
+
+@Singleton
+public class PackageDocs
+ @Inject constructor(val linkResolver: DeclarationLinkResolver?,
+ val logger: DokkaLogger)
+{
+ public val moduleContent: MutableContent = MutableContent()
+ private val _packageContent: MutableMap<String, MutableContent> = hashMapOf()
+ public val packageContent: Map<String, Content>
+ get() = _packageContent
+
+ fun parse(fileName: String, linkResolveContext: LazyPackageDescriptor?) {
+ val file = File(fileName)
+ if (file.exists()) {
+ val text = file.readText()
+ val tree = parseMarkdown(text)
+ var targetContent: MutableContent = moduleContent
+ tree.children.forEach {
+ if (it.type == MarkdownElementTypes.ATX_1) {
+ val headingText = it.child(MarkdownTokenTypes.ATX_CONTENT)?.text
+ if (headingText != null) {
+ targetContent = findTargetContent(headingText.trimStart())
+ }
+ } else {
+ buildContentTo(it, targetContent, { resolveContentLink(it, linkResolveContext) })
+ }
+ }
+ } else {
+ logger.warn("Include file $file was not found.")
+ }
+ }
+
+ private fun findTargetContent(heading: String): MutableContent {
+ if (heading.startsWith("Module") || heading.startsWith("module")) {
+ return moduleContent
+ }
+ if (heading.startsWith("Package") || heading.startsWith("package")) {
+ return findOrCreatePackageContent(heading.substring("package".length).trim())
+ }
+ return findOrCreatePackageContent(heading)
+ }
+
+ private fun findOrCreatePackageContent(packageName: String) =
+ _packageContent.getOrPut(packageName) { -> MutableContent() }
+
+ private fun resolveContentLink(href: String, linkResolveContext: LazyPackageDescriptor?): ContentBlock {
+ if (linkResolveContext != null && linkResolver != null) {
+ return linkResolver.resolveContentLink(linkResolveContext, href)
+ }
+ return ContentExternalLink("#")
+ }
+} \ No newline at end of file
diff --git a/core/src/main/kotlin/Model/SourceLinks.kt b/core/src/main/kotlin/Model/SourceLinks.kt
new file mode 100644
index 00000000..956bfe4b
--- /dev/null
+++ b/core/src/main/kotlin/Model/SourceLinks.kt
@@ -0,0 +1,56 @@
+package org.jetbrains.dokka
+
+import com.intellij.psi.PsiElement
+import java.io.File
+import com.intellij.psi.PsiDocumentManager
+import com.intellij.psi.PsiNameIdentifierOwner
+import org.jetbrains.kotlin.psi.psiUtil.startOffset
+
+class SourceLinkDefinition(val path: String, val url: String, val lineSuffix: String?)
+
+fun DocumentationNode.appendSourceLink(psi: PsiElement?, sourceLinks: List<SourceLinkDefinition>) {
+ val path = psi?.containingFile?.virtualFile?.path ?: return
+
+ val target = if (psi is PsiNameIdentifierOwner) psi.nameIdentifier else psi
+ val absPath = File(path).absolutePath
+ val linkDef = sourceLinks.firstOrNull { absPath.startsWith(it.path) }
+ if (linkDef != null) {
+ var url = linkDef.url + path.substring(linkDef.path.length)
+ if (linkDef.lineSuffix != null) {
+ val line = target?.lineNumber()
+ if (line != null) {
+ url += linkDef.lineSuffix + line.toString()
+ }
+ }
+ append(DocumentationNode(url, Content.Empty, DocumentationNode.Kind.SourceUrl),
+ DocumentationReference.Kind.Detail);
+ }
+
+ if (target != null) {
+ append(DocumentationNode(target.sourcePosition(), Content.Empty, DocumentationNode.Kind.SourcePosition), DocumentationReference.Kind.Detail)
+ }
+}
+
+private fun PsiElement.sourcePosition(): String {
+ val path = containingFile.virtualFile.path
+ val lineNumber = lineNumber()
+ val columnNumber = columnNumber()
+
+ return when {
+ lineNumber == null -> path
+ columnNumber == null -> "$path:$lineNumber"
+ else -> "$path:$lineNumber:$columnNumber"
+ }
+}
+
+fun PsiElement.lineNumber(): Int? {
+ val doc = PsiDocumentManager.getInstance(project).getDocument(containingFile)
+ // IJ uses 0-based line-numbers; external source browsers use 1-based
+ return doc?.getLineNumber(textRange.startOffset)?.plus(1)
+}
+
+fun PsiElement.columnNumber(): Int? {
+ val doc = PsiDocumentManager.getInstance(project).getDocument(containingFile) ?: return null
+ val lineNumber = doc.getLineNumber(textRange.startOffset)
+ return startOffset - doc.getLineStartOffset(lineNumber)
+} \ No newline at end of file