diff options
Diffstat (limited to 'src/Java/JavadocParser.kt')
-rw-r--r-- | src/Java/JavadocParser.kt | 170 |
1 files changed, 170 insertions, 0 deletions
diff --git a/src/Java/JavadocParser.kt b/src/Java/JavadocParser.kt new file mode 100644 index 00000000..1378a5a7 --- /dev/null +++ b/src/Java/JavadocParser.kt @@ -0,0 +1,170 @@ +package org.jetbrains.dokka + +import com.intellij.psi.* +import com.intellij.psi.javadoc.PsiDocTag +import com.intellij.psi.javadoc.PsiDocTagValue +import com.intellij.psi.javadoc.PsiDocToken +import com.intellij.psi.javadoc.PsiInlineDocTag +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import org.jsoup.nodes.Node +import org.jsoup.nodes.TextNode + +data class JavadocParseResult(val content: Content, val deprecatedContent: Content?) { + companion object { + val Empty = JavadocParseResult(Content.Empty, null) + } +} + +interface JavaDocumentationParser { + fun parseDocumentation(element: PsiNamedElement): JavadocParseResult +} + +class JavadocParser(private val refGraph: NodeReferenceGraph) : JavaDocumentationParser { + override fun parseDocumentation(element: PsiNamedElement): JavadocParseResult { + val docComment = (element as? PsiDocCommentOwner)?.docComment + if (docComment == null) return JavadocParseResult.Empty + val result = MutableContent() + var deprecatedContent: Content? = null + val para = ContentParagraph() + result.append(para) + para.convertJavadocElements(docComment.descriptionElements.dropWhile { it.text.trim().isEmpty() }) + docComment.tags.forEach { tag -> + when(tag.name) { + "see" -> result.convertSeeTag(tag) + "deprecated" -> { + deprecatedContent = Content() + deprecatedContent!!.convertJavadocElements(tag.contentElements()) + } + else -> { + val subjectName = tag.getSubjectName() + val section = result.addSection(javadocSectionDisplayName(tag.name), subjectName) + + section.convertJavadocElements(tag.contentElements()) + } + } + } + return JavadocParseResult(result, deprecatedContent) + } + + private fun PsiDocTag.contentElements(): Iterable<PsiElement> { + val tagValueElements = children + .dropWhile { it.node?.elementType == JavaDocTokenType.DOC_TAG_NAME } + .dropWhile { it is PsiWhiteSpace } + .filterNot { it.node?.elementType == JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS } + return if (getSubjectName() != null) tagValueElements.dropWhile { it is PsiDocTagValue } else tagValueElements + } + + private fun ContentBlock.convertJavadocElements(elements: Iterable<PsiElement>) { + val htmlBuilder = StringBuilder() + elements.forEach { + if (it is PsiInlineDocTag) { + htmlBuilder.append(convertInlineDocTag(it)) + } else { + htmlBuilder.append(it.text) + } + } + val doc = Jsoup.parse(htmlBuilder.toString().trimStart()) + doc.body().childNodes().forEach { + convertHtmlNode(it) + } + } + + private fun ContentBlock.convertHtmlNode(node: Node) { + if (node is TextNode) { + append(ContentText(node.text())) + } else if (node is Element) { + val childBlock = createBlock(node) + node.childNodes().forEach { + childBlock.convertHtmlNode(it) + } + append(childBlock) + } + } + + private fun createBlock(element: Element): ContentBlock = when(element.tagName()) { + "p" -> ContentParagraph() + "b", "strong" -> ContentStrong() + "i", "em" -> ContentEmphasis() + "s", "del" -> ContentStrikethrough() + "code" -> ContentCode() + "pre" -> ContentBlockCode() + "ul" -> ContentUnorderedList() + "ol" -> ContentOrderedList() + "li" -> ContentListItem() + "a" -> createLink(element) + else -> ContentBlock() + } + + private fun createLink(element: Element): ContentBlock { + val docref = element.attr("docref") + if (docref != null) { + return ContentNodeLazyLink(docref, { -> refGraph.lookup(docref)}) + } + val href = element.attr("href") + if (href != null) { + return ContentExternalLink(href) + } else { + return ContentBlock() + } + } + + private fun MutableContent.convertSeeTag(tag: PsiDocTag) { + val linkElement = tag.linkElement() + if (linkElement == null) { + return + } + val seeSection = findSectionByTag(ContentTags.SeeAlso) ?: addSection(ContentTags.SeeAlso, null) + val linkSignature = resolveLink(linkElement) + val text = ContentText(linkElement.text) + if (linkSignature != null) { + val linkNode = ContentNodeLazyLink(tag.valueElement!!.text, { -> refGraph.lookup(linkSignature)}) + linkNode.append(text) + seeSection.append(linkNode) + } else { + seeSection.append(text) + } + } + + private fun convertInlineDocTag(tag: PsiInlineDocTag) = when (tag.name) { + "link", "linkplain" -> { + val valueElement = tag.linkElement() + val linkSignature = resolveLink(valueElement) + if (linkSignature != null) { + val labelText = tag.dataElements.firstOrNull { it is PsiDocToken }?.text ?: valueElement!!.text + val link = "<a docref=\"$linkSignature\">${labelText.htmlEscape()}</a>" + if (tag.name == "link") "<code>$link</code>" else link + } + else if (valueElement != null) { + valueElement.text + } else { + "" + } + } + "code", "literal" -> { + val text = StringBuilder() + tag.dataElements.forEach { text.append(it.text) } + val escaped = text.toString().trimStart().htmlEscape() + if (tag.name == "code") "<code>$escaped</code>" else escaped + } + else -> tag.text + } + + private fun PsiDocTag.linkElement(): PsiElement? = + valueElement ?: dataElements.firstOrNull { it !is PsiWhiteSpace } + + private fun resolveLink(valueElement: PsiElement?): String? { + val target = valueElement?.reference?.resolve() + if (target != null) { + return getSignature(target) + } + return null + } + + fun PsiDocTag.getSubjectName(): String? { + if (name == "param" || name == "throws" || name == "exception") { + return valueElement?.text + } + return null + } +} |