aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Analysis/CommentsAPI.kt41
-rw-r--r--src/Formats/StructuredFormatService.kt39
-rw-r--r--src/Kotlin/ContentBuilder.kt48
-rw-r--r--src/Kotlin/DocumentationBuilder.kt35
-rw-r--r--src/Markdown/MarkdownProcessor.kt6
-rw-r--r--src/Model/Content.kt67
-rw-r--r--src/Model/DocumentationNode.kt10
-rw-r--r--src/main.kt2
8 files changed, 106 insertions, 142 deletions
diff --git a/src/Analysis/CommentsAPI.kt b/src/Analysis/CommentsAPI.kt
deleted file mode 100644
index ce453113..00000000
--- a/src/Analysis/CommentsAPI.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-package org.jetbrains.dokka
-
-import org.jetbrains.kotlin.descriptors.*
-import org.jetbrains.kotlin.resolve.*
-import org.jetbrains.kotlin.kdoc.psi.api.*
-
-/**
- * Retrieves PSI elements representing documentation from the [DeclarationDescriptor]
- *
- * $$receiver: [DeclarationDescriptor] to get documentation nodes from
- */
-fun DeclarationDescriptor.getDocumentationElements(): List<KDoc> {
- val psiElement = DescriptorToSourceUtils.descriptorToDeclaration(this)
- if (psiElement == null)
- return listOf()
-
- return psiElement.children() // visit children
- .takeWhile { it is KDoc } // all KDoc
- .map { it as KDoc } // cast
- .toList()
- .reverse() // make reversed list
-}
-
-/**
- * Extracts text from KDoc, removes comment symbols and trims whitespace
- */
-fun KDoc?.extractText(): String {
- if (this == null)
- return ""
- val text = getText()
- if (text == null)
- return ""
- val lines = text.replace("\r", "").split("\n")
- return lines.map {
- val comment = it.trim().dropWhile { it == '/' || it == '*' }
- (if (comment.endsWith("*/"))
- comment.substring(0, comment.length() - 2)
- else
- comment).trim()
- }.join("\n").trim("\n")
-} \ No newline at end of file
diff --git a/src/Formats/StructuredFormatService.kt b/src/Formats/StructuredFormatService.kt
index d2fbdc10..6ec75379 100644
--- a/src/Formats/StructuredFormatService.kt
+++ b/src/Formats/StructuredFormatService.kt
@@ -81,36 +81,49 @@ public abstract class StructuredFormatService(val locationService: LocationServi
}
fun appendDescription(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
- val described = nodes.filter { it.hasDescription() }
+ val described = nodes.filter { it.hasDescriptionOrTags() }
if (described.any()) {
val single = described.size() == 1
- appendHeader(to, "Description", 3)
+ if (described.any { it.content.description != ContentEmpty }) {
+ appendHeader(to, "Description", 3)
+ }
for (node in described) {
if (!single) {
appendBlockCode(to, formatText(location, languageService.render(node)))
}
appendLine(to, formatText(location, node.content.description))
appendLine(to)
- for ((label, section) in node.content.sections) {
- if (!isDescriptionSection(label, node)) continue
- appendLine(to, formatStrong(formatText(label)))
+
+ node.content.getSectionsWithSubjects().forEach {
+ appendSectionWithSubject(it.getKey(), location, it.getValue(), to)
+ }
+
+ for (section in node.content.sections.filter { it.subjectName == null }) {
+ appendLine(to, formatStrong(formatText(section.tag)))
appendLine(to, formatText(location, section))
}
}
}
}
- private fun DocumentationNode.hasDescription() =
- content.description != ContentEmpty || content.sections.any { isDescriptionSection(it.key, this) }
+ fun Content.getSectionsWithSubjects(): Map<String, List<ContentSection>> =
+ sections.filter { it.subjectName != null }.groupBy { it.tag }
- private fun isDescriptionSection(label: String, node: DocumentationNode): Boolean {
- if (label.startsWith("$"))
- return false
- if (node.members.any { it.name == label })
- return false
- return true
+ fun appendSectionWithSubject(title: String, location: Location, subjectSections: List<ContentSection>, to: StringBuilder) {
+ appendHeader(to, title, 3)
+ subjectSections.forEach {
+ val subjectName = it.subjectName
+ if (subjectName != null) {
+ to.append(formatCode(subjectName)).append(" - ")
+ to.append(formatText(location, it))
+ appendLine(to)
+ }
+ }
}
+ private fun DocumentationNode.hasDescriptionOrTags() =
+ content.description != ContentEmpty || !content.sections.isEmpty()
+
fun appendSummary(location: Location, to: StringBuilder, nodes: Iterable<DocumentationNode>) {
val breakdownBySummary = nodes.groupByTo(LinkedHashMap()) { node ->
formatText(location, node.summary)
diff --git a/src/Kotlin/ContentBuilder.kt b/src/Kotlin/ContentBuilder.kt
index c943a505..35beec54 100644
--- a/src/Kotlin/ContentBuilder.kt
+++ b/src/Kotlin/ContentBuilder.kt
@@ -9,10 +9,16 @@ import org.intellij.markdown.*
import org.jetbrains.kotlin.psi.JetDeclarationWithBody
import org.jetbrains.kotlin.psi.JetBlockExpression
-public fun DocumentationBuilder.buildContent(tree: MarkdownNode, descriptor: DeclarationDescriptor): Content {
+public fun DocumentationBuilder.buildContent(tree: MarkdownNode): Content {
+ val result = Content()
+ buildContentTo(tree, result)
+ return result
+}
+
+public fun DocumentationBuilder.buildContentTo(tree: MarkdownNode, target: ContentNode) {
// println(tree.toTestString())
val nodeStack = ArrayDeque<ContentNode>()
- nodeStack.push(Content())
+ nodeStack.push(target)
tree.visit {(node, processChildren) ->
val parent = nodeStack.peek()!!
@@ -47,22 +53,6 @@ public fun DocumentationBuilder.buildContent(tree: MarkdownNode, descriptor: Dec
processChildren()
parent.append(nodeStack.pop())
}
- MarkdownTokenTypes.SECTION_ID -> {
- if (parent !is ContentSection) {
- val text = node.text.trimLeading("$").trim("{", "}")
- val name = text.substringBefore(" ")
- val params = text.substringAfter(" ")
- when (name) {
- "code" -> parent.append(functionBody(descriptor, params))
- }
- }
- }
- MarkdownElementTypes.SECTION -> {
- val label = node.child(MarkdownTokenTypes.SECTION_ID)?.let { it.text.trimLeading("$").trim("{", "}") } ?: ""
- nodeStack.push(ContentSection(label))
- processChildren()
- parent.append(nodeStack.pop())
- }
MarkdownElementTypes.INLINE_LINK -> {
val label = node.child(MarkdownElementTypes.LINK_TEXT)?.child(MarkdownTokenTypes.TEXT)
val destination = node.child(MarkdownElementTypes.LINK_DESTINATION)
@@ -88,7 +78,7 @@ public fun DocumentationBuilder.buildContent(tree: MarkdownNode, descriptor: Dec
}
MarkdownTokenTypes.WHITE_SPACE,
MarkdownTokenTypes.EOL -> {
- if (nodeStack.peek() is ContentParagraph && node.parent?.children?.last() != node) {
+ if (keepWhitespace(nodeStack.peek()) && node.parent?.children?.last() != node) {
nodeStack.push(ContentText(node.text))
processChildren()
parent.append(nodeStack.pop())
@@ -105,10 +95,7 @@ public fun DocumentationBuilder.buildContent(tree: MarkdownNode, descriptor: Dec
parent.append(nodeStack.pop())
}
MarkdownTokenTypes.COLON -> {
- // TODO fix markdown parser
- if (!isColonAfterSectionLabel(node)) {
- parent.append(ContentText(node.text))
- }
+ parent.append(ContentText(node.text))
}
MarkdownTokenTypes.DOUBLE_QUOTE,
MarkdownTokenTypes.LT,
@@ -120,9 +107,16 @@ public fun DocumentationBuilder.buildContent(tree: MarkdownNode, descriptor: Dec
}
}
}
- return nodeStack.pop() as Content
}
+private fun keepWhitespace(node: ContentNode) = node is ContentParagraph || node is ContentSection
+
+public fun DocumentationBuilder.buildInlineContentTo(tree: MarkdownNode, target: ContentNode) {
+ val inlineContent = tree.children.singleOrNull { it.type == MarkdownElementTypes.PARAGRAPH }?.children ?: listOf(tree)
+ inlineContent.forEach {
+ buildContentTo(it, target)
+ }
+}
fun DocumentationBuilder.functionBody(descriptor: DeclarationDescriptor, functionName: String): ContentNode {
val scope = getResolutionScope(descriptor)
@@ -176,9 +170,3 @@ private fun DocumentationBuilder.resolveInScope(functionName: String, scope: Jet
return symbol
}
-
-private fun isColonAfterSectionLabel(node: MarkdownNode): Boolean {
- val parent = node.parent
- return parent != null && parent.type == MarkdownElementTypes.SECTION && parent.children.size() >= 2 &&
- node == parent.children[1];
-}
diff --git a/src/Kotlin/DocumentationBuilder.kt b/src/Kotlin/DocumentationBuilder.kt
index be6d5d7c..c0533437 100644
--- a/src/Kotlin/DocumentationBuilder.kt
+++ b/src/Kotlin/DocumentationBuilder.kt
@@ -18,6 +18,10 @@ import com.intellij.psi.PsiNameIdentifierOwner
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.psi.JetParameter
+import org.jetbrains.kotlin.kdoc.findKDoc
+import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection
+import com.intellij.psi.util.PsiTreeUtil
+import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
public data class DocumentationOptions(val includeNonPublic: Boolean = false,
val sourceLinks: List<SourceLinkDefinition>)
@@ -36,13 +40,38 @@ class DocumentationBuilder(val session: ResolveSession, val options: Documentati
val packages = hashMapOf<FqName, DocumentationNode>()
fun parseDocumentation(descriptor: DeclarationDescriptor): Content {
- val docText = descriptor.getDocumentationElements().map { it.extractText() }.join("\n")
- val tree = parseMarkdown(docText)
+ val kdoc = findKDoc(descriptor)
+ if (kdoc == null) {
+ return Content.Empty
+ }
+ val tree = parseMarkdown(kdoc.getContent())
//println(tree.toTestString())
- val content = buildContent(tree, descriptor)
+ val content = buildContent(tree)
+ if (kdoc is KDocSection) {
+ val tags = kdoc.getTags()
+ tags.forEach {
+ if (it.getName() == "code") {
+ content.append(functionBody(descriptor, it.getContent()))
+ } else {
+ val section = content.addSection(displayName(it.getName()), it.getSubjectName())
+ val sectionContent = it.getContent()
+ val markdownNode = parseMarkdown(sectionContent)
+ buildInlineContentTo(markdownNode, section)
+ }
+ }
+ }
return content
}
+ fun KDocSection.getTags(): Array<KDocTag> = PsiTreeUtil.getChildrenOfType(this, javaClass<KDocTag>()) ?: array()
+
+ fun displayName(sectionName: String?): String? =
+ when(sectionName) {
+ "param" -> "Parameters"
+ "throws", "exception" -> "Exceptions"
+ else -> sectionName?.capitalize()
+ }
+
fun link(node: DocumentationNode, descriptor: DeclarationDescriptor) {
links.put(node, descriptor)
}
diff --git a/src/Markdown/MarkdownProcessor.kt b/src/Markdown/MarkdownProcessor.kt
index 05c4a7ec..7e601905 100644
--- a/src/Markdown/MarkdownProcessor.kt
+++ b/src/Markdown/MarkdownProcessor.kt
@@ -3,7 +3,7 @@ package org.jetbrains.dokka
import org.intellij.markdown.*
import org.intellij.markdown.ast.*
import org.intellij.markdown.parser.*
-import org.intellij.markdown.parser.dialects.KDocMarkerProcessor
+import org.intellij.markdown.parser.dialects.commonmark.CommonMarkMarkerProcessor
class MarkdownNode(val node: ASTNode, val parent: MarkdownNode?, val markdown: String) {
val children: List<MarkdownNode> = node.children.map { MarkdownNode(it, this, markdown) }
@@ -134,12 +134,12 @@ public fun MarkdownNode.toHtml(): String {
fun parseMarkdown(markdown: String): MarkdownNode {
if (markdown.isEmpty())
return MarkdownNode(LeafASTNode(MarkdownElementTypes.MARKDOWN_FILE, 0, 0), null, markdown)
- return MarkdownNode(MarkdownParser(KDocMarkerProcessor.Factory()).buildMarkdownTreeFromString(markdown), null, markdown)
+ return MarkdownNode(MarkdownParser(CommonMarkMarkerProcessor.Factory()).buildMarkdownTreeFromString(markdown), null, markdown)
}
fun markdownToHtml(markdown: String): String {
- val tree = MarkdownParser(KDocMarkerProcessor.Factory()).buildMarkdownTreeFromString(markdown)
+ val tree = MarkdownParser(CommonMarkMarkerProcessor.Factory()).buildMarkdownTreeFromString(markdown)
val markdownTree = MarkdownNode(tree, null, markdown)
val ast = markdownTree.toTestString()
return markdownTree.toHtml()
diff --git a/src/Model/Content.kt b/src/Model/Content.kt
index 8491fd88..e59037d2 100644
--- a/src/Model/Content.kt
+++ b/src/Model/Content.kt
@@ -34,7 +34,7 @@ public class ContentNodeLink(val node : DocumentationNode) : ContentBlock()
public class ContentExternalLink(val href : String) : ContentBlock()
public class ContentList() : ContentBlock()
public class ContentListItem() : ContentBlock()
-public class ContentSection(public val label: String) : ContentBlock()
+public class ContentSection(public val tag: String, public val subjectName: String?) : ContentBlock()
fun content(body: ContentNode.() -> Unit): ContentNode {
val block = ContentBlock()
@@ -54,53 +54,36 @@ fun ContentNode.link(to: DocumentationNode, body: ContentNode.() -> Unit) {
}
public class Content() : ContentNode() {
- public val sections: Map<String, ContentSection> by Delegates.lazy {
- val map = linkedMapOf<String, ContentSection>()
- for (child in children) {
- if (child is ContentSection)
- map.put(child.label, child)
- }
-
- if ("\$summary" !in map && "\$description" !in map) {
- // no explicit summary and description, convert anonymous section
- val anonymous = map[""]
- if (anonymous != null) {
- map.remove("")
- val summary = ContentSection("\$summary")
- val description = ContentSection("\$description")
-
- val summaryNodes = anonymous.children.take(1)
- val descriptionNodes = anonymous.children.drop(1)
-
- if (summaryNodes.any()) {
- summary.children.addAll(summaryNodes)
- map.put("\$summary", summary)
- }
-
- if (descriptionNodes.any()) {
- description.children.addAll(descriptionNodes)
- map.put("\$description", description)
- }
- }
- }
- map
+ private val sectionList = arrayListOf<ContentSection>()
+ public val sections: List<ContentSection>
+ get() = sectionList
+
+ fun addSection(name: String?, subjectName: String?): ContentSection {
+ val section = ContentSection(name ?: "", subjectName)
+ sectionList.add(section)
+ return section
}
- public val summary: ContentNode get() {
- return sections["\$summary"] ?: ContentNode.empty
+ fun findSectionByTag(tag: String): ContentSection? =
+ sections.firstOrNull { tag.equalsIgnoreCase(it.tag) }
+
+ public val summary: ContentNode get() = children.firstOrNull() ?: ContentEmpty
+
+ public val description: ContentNode by Delegates.lazy {
+ val descriptionNodes = children.drop(1)
+ if (descriptionNodes.isEmpty()) {
+ ContentEmpty
+ } else {
+ val result = ContentSection("Description", null)
+ result.children.addAll(descriptionNodes)
+ result
+ }
}
- public val description: ContentNode get() = sections["\$description"] ?: ContentNode.empty
override fun equals(other: Any?): Boolean {
if (other !is Content)
return false
- if (sections.size != other.sections.size)
- return false
- for (keys in sections.keySet())
- if (sections[keys] != other.sections[keys])
- return false
-
- return true
+ return sections == other.sections && children == other.children
}
override fun hashCode(): Int {
@@ -110,7 +93,7 @@ public class Content() : ContentNode() {
override fun toString(): String {
if (sections.isEmpty())
return "<empty>"
- return sections.values().joinToString()
+ return (listOf(summary, description) + sections).joinToString()
}
val isEmpty: Boolean
diff --git a/src/Model/DocumentationNode.kt b/src/Model/DocumentationNode.kt
index a666b486..c3700069 100644
--- a/src/Model/DocumentationNode.kt
+++ b/src/Model/DocumentationNode.kt
@@ -8,15 +8,7 @@ public open class DocumentationNode(val name: String,
private val references = LinkedHashSet<DocumentationReference>()
- public val summary: ContentNode get() {
- val contentSection = content.sections["\$summary"]
- if (contentSection != null)
- return contentSection
- val ownerSection = owner?.content?.sections?.get(name)
- if (ownerSection != null)
- return ownerSection
- return ContentNode.empty
- }
+ public val summary: ContentNode get() = content.summary
public val owner: DocumentationNode?
get() = references(DocumentationReference.Kind.Owner).singleOrNull()?.to
diff --git a/src/main.kt b/src/main.kt
index b075d4e9..9ebe0b1e 100644
--- a/src/main.kt
+++ b/src/main.kt
@@ -111,7 +111,7 @@ public fun main(args: Array<String>) {
if (file.exists()) {
val text = file.readText()
val tree = parseMarkdown(text)
- val content = buildContent(tree, session.getPackageFragment(FqName.ROOT))
+ val content = buildContent(tree)
moduleContent.children.addAll(content.children)
} else {
println("WARN: Include file $file was not found.")