diff options
author | Błażej Kardyś <bkardys@virtuslab.com> | 2020-07-17 03:48:03 +0200 |
---|---|---|
committer | Sebastian Sellmair <34319766+sellmair@users.noreply.github.com> | 2020-08-11 14:27:44 +0200 |
commit | aeb2014eee704be377c06205d16f60562d2a8cf1 (patch) | |
tree | d144b627a0b379bebedf7e015f6d469fea61d0bb /plugins/base/src/main | |
parent | 49c9bcc586abb7c78f569526a05fea97da86993d (diff) | |
download | dokka-aeb2014eee704be377c06205d16f60562d2a8cf1.tar.gz dokka-aeb2014eee704be377c06205d16f60562d2a8cf1.tar.bz2 dokka-aeb2014eee704be377c06205d16f60562d2a8cf1.zip |
Fixing javadoc comment parser for psi files
Diffstat (limited to 'plugins/base/src/main')
3 files changed, 121 insertions, 71 deletions
diff --git a/plugins/base/src/main/kotlin/transformers/pages/comments/DocTagToContentConverter.kt b/plugins/base/src/main/kotlin/transformers/pages/comments/DocTagToContentConverter.kt index 0f953e0f..9d667623 100644 --- a/plugins/base/src/main/kotlin/transformers/pages/comments/DocTagToContentConverter.kt +++ b/plugins/base/src/main/kotlin/transformers/pages/comments/DocTagToContentConverter.kt @@ -53,6 +53,9 @@ object DocTagToContentConverter : CommentsToContentConverter { ) ) + fun P.collapseParagraphs(): P = + if (children.size == 1 && children.first() is P) (children.first() as P).collapseParagraphs() else this + return when (docTag) { is H1 -> buildHeader(1) is H2 -> buildHeader(2) @@ -63,12 +66,14 @@ object DocTagToContentConverter : CommentsToContentConverter { is Ul -> buildList(false) is Ol -> buildList(true, docTag.params["start"]?.toInt() ?: 1) is Li -> listOf( - ContentGroup(children = buildChildren(docTag), dci, sourceSets, styles, extra) + ContentGroup(buildChildren(docTag), dci, sourceSets, styles, extra) ) is Br -> buildNewLine() is B -> buildChildren(docTag, setOf(TextStyle.Strong)) is I -> buildChildren(docTag, setOf(TextStyle.Italic)) - is P -> buildChildren(docTag, newStyles = setOf(TextStyle.Paragraph)) + is P -> listOf( + ContentGroup(buildChildren(docTag.collapseParagraphs()), dci, sourceSets, styles + setOf(TextStyle.Paragraph), extra) + ) is A -> listOf( ContentResolvedLink( buildChildren(docTag), diff --git a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt index df5d4ee1..9ed37c30 100644 --- a/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt +++ b/plugins/base/src/main/kotlin/translators/psi/DefaultPsiToDocumentableTranslator.kt @@ -285,8 +285,7 @@ class DefaultPsiToDocumentableTranslator( psiParameter.name, DocumentationNode( listOfNotNull(docs.firstChildOfTypeOrNull<Param> { - it.firstChildOfTypeOrNull<DocumentationLink>() - ?.firstChildOfTypeOrNull<Text>()?.body == psiParameter.name + it.name == psiParameter.name })).toSourceSetDependent(), null, getBound(psiParameter.type), diff --git a/plugins/base/src/main/kotlin/translators/psi/JavadocParser.kt b/plugins/base/src/main/kotlin/translators/psi/JavadocParser.kt index 81955fde..8262b3c6 100644 --- a/plugins/base/src/main/kotlin/translators/psi/JavadocParser.kt +++ b/plugins/base/src/main/kotlin/translators/psi/JavadocParser.kt @@ -34,18 +34,30 @@ class JavadocParser( docComment.getDescription()?.let { nodes.add(it) } nodes.addAll(docComment.tags.mapNotNull { tag -> when (tag.name) { - "param" -> Param(P(convertJavadocElements(tag.dataElements.toList())), tag.text) - "throws" -> Throws(P(convertJavadocElements(tag.dataElements.toList())), tag.text) - "return" -> Return(P(convertJavadocElements(tag.dataElements.toList()))) - "author" -> Author(P(convertJavadocElements(tag.dataElements.toList()))) - "see" -> See(P(getSeeTagElementContent(tag)), tag.referenceElement()?.text.orEmpty(), null) - "deprecated" -> Deprecated(P(convertJavadocElements(tag.dataElements.toList()))) + "param" -> Param( + wrapTagIfNecessary(convertJavadocElements(tag.contentElements())), + tag.children.firstIsInstanceOrNull<PsiDocParamRef>()?.text.orEmpty() + ) + "throws" -> Throws(wrapTagIfNecessary(convertJavadocElements(tag.contentElements())), tag.text) + "return" -> Return(wrapTagIfNecessary(convertJavadocElements(tag.contentElements()))) + "author" -> Author(wrapTagIfNecessary(convertJavadocElements(tag.contentElements()))) + "see" -> getSeeTagElementContent(tag).let { + See( + wrapTagIfNecessary(it.first), + tag.referenceElement()?.text.orEmpty(), + it.second + ) + } + "deprecated" -> Deprecated(wrapTagIfNecessary(convertJavadocElements(tag.dataElements.toList()))) else -> null } }) return DocumentationNode(nodes) } + private fun wrapTagIfNecessary(list: List<DocTag>): DocTag = + if (list.size == 1) list.first() else P(list) + private fun findClosestDocComment(element: PsiNamedElement): PsiDocComment? { (element as? PsiDocCommentOwner)?.docComment?.run { return this } if (element is PsiMethod) { @@ -119,88 +131,118 @@ class JavadocParser( } } - private fun getSeeTagElementContent(tag: PsiDocTag): List<DocTag> = - listOfNotNull(tag.referenceElement()?.toDocumentationLink()) + private fun getSeeTagElementContent(tag: PsiDocTag): Pair<List<DocumentationLink>, DRI?> { + val content = tag.referenceElement()?.toDocumentationLink() + return Pair(listOfNotNull(content), content?.dri) + } private fun PsiDocComment.getDescription(): Description? { - val nonEmptyDescriptionElements = descriptionElements.filter { it.text.trim().isNotEmpty() } - val convertedDescriptionElements = convertJavadocElements(nonEmptyDescriptionElements) - if (convertedDescriptionElements.isNotEmpty()) { - return Description(P(convertedDescriptionElements)) + val nonEmptyDescriptionElements = descriptionElements.filter { it.text.isNotBlank() } + return convertJavadocElements(nonEmptyDescriptionElements).takeIf { it.isNotEmpty() }?.let { + Description(wrapTagIfNecessary(it)) } - - return null } - private fun convertJavadocElements(elements: Iterable<PsiElement>): List<DocTag> = - elements.mapNotNull { - when (it) { - is PsiReference -> convertJavadocElements(it.children.toList()) - is PsiInlineDocTag -> listOfNotNull(convertInlineDocTag(it)) - is PsiDocParamRef -> listOfNotNull(it.toDocumentationLink()) - is PsiDocTagValue, - is LeafPsiElement -> Jsoup.parse(it.text.trim()).body().childNodes().mapNotNull(::convertHtmlNode) - else -> null + private inner class Parse : (Iterable<PsiElement>, Boolean) -> List<DocTag> { + val driMap = mutableMapOf<String, DRI>() + + private fun PsiElement.stringify(): String? = when (this) { + is PsiReference -> children.joinToString("") { it.stringify().orEmpty() } + is PsiInlineDocTag -> convertInlineDocTag(this) + is PsiDocParamRef -> toDocumentationLinkString() + is PsiDocTagValue, + is LeafPsiElement -> (if ((prevSibling as? PsiDocToken)?.isLeadingAsterisk() == true) text?.trim() else text)?.takeUnless { it.isBlank() } + else -> null + } + + private fun PsiElement.toDocumentationLinkString( + labelElement: PsiElement? = null + ): String? = + reference?.resolve()?.let { + if (it !is PsiParameter) { + val dri = DRI.from(it) + driMap[dri.toString()] = dri + val label = labelElement ?: defaultLabel() + """<a data-dri=$dri>${label.text}</a>""" + } else null } - }.flatten() - private fun convertHtmlNode(node: Node, insidePre: Boolean = false): DocTag? = when (node) { - is TextNode -> Text(body = if (insidePre) node.wholeText else node.text()) - is Element -> createBlock(node) - else -> null - } + private fun convertInlineDocTag(tag: PsiInlineDocTag) = when (tag.name) { + "link", "linkplain" -> { + tag.referenceElement()?.toDocumentationLinkString(tag.dataElements.firstIsInstanceOrNull<PsiDocToken>()) + } + "code", "literal" -> { + "<code data-inline>${tag.text}</code>" + } + "index" -> "<index>${tag.children.filterIsInstance<PsiDocTagValue>().joinToString { it.text }}</index>" + else -> tag.text + } - private fun createBlock(element: Element): DocTag { - val children = element.childNodes().mapNotNull { convertHtmlNode(it) } - return when (element.tagName()) { - "p" -> P(listOf(Br, Br) + children) - "b" -> B(children) - "strong" -> Strong(children) - "i" -> I(children) - "em" -> Em(children) - "code" -> CodeBlock(children) - "pre" -> Pre(children) - "ul" -> Ul(children) - "ol" -> Ol(children) - "li" -> Li(children) - "a" -> createLink(element, children) - else -> Text(body = element.ownText()) + private fun createLink(element: Element, children: List<DocTag>): DocTag { + return when { + element.hasAttr("docref") -> + A(children, params = mapOf("docref" to element.attr("docref"))) + element.hasAttr("href") -> + A(children, params = mapOf("href" to element.attr("href"))) + element.hasAttr("data-dri") && driMap.containsKey(element.attr("data-dri")) -> + DocumentationLink(driMap[element.attr("data-dri")]!!, children) + else -> Text(children = children) + } } - } - private fun createLink(element: Element, children: List<DocTag>): DocTag { - return when { - element.hasAttr("docref") -> { - A(children, params = mapOf("docref" to element.attr("docref"))) + private fun createBlock(element: Element): DocTag? { + val children = element.childNodes().mapNotNull { convertHtmlNode(it) } + fun ifChildrenPresent(operation: () -> DocTag): DocTag? { + return if (children.isNotEmpty()) operation() else null } - element.hasAttr("href") -> { - A(children, params = mapOf("href" to element.attr("href"))) + return when (element.tagName()) { + "blockquote" -> ifChildrenPresent { BlockQuote(children) } + "p" -> ifChildrenPresent { P(children) } + "b" -> ifChildrenPresent { B(children) } + "strong" -> ifChildrenPresent { Strong(children) } + "index" -> Index(children) + "i" -> ifChildrenPresent { I(children) } + "em" -> Em(children) + "code" -> ifChildrenPresent { + if (element.hasAttr("data-inline")) CodeInline(children) else CodeBlock( + children + ) + } + "pre" -> Pre(children) + "ul" -> ifChildrenPresent { Ul(children) } + "ol" -> ifChildrenPresent { Ol(children) } + "li" -> Li(children) + "a" -> createLink(element, children) + else -> Text(body = element.ownText()) } - else -> Text(children = children) } + + private fun convertHtmlNode(node: Node, insidePre: Boolean = false): DocTag? = when (node) { + is TextNode -> Text(body = if (insidePre) node.wholeText else node.text()) + is Element -> createBlock(node) + else -> null + } + + override fun invoke(elements: Iterable<PsiElement>, asParagraph: Boolean): List<DocTag> = + Jsoup.parseBodyFragment(elements.mapNotNull { it.stringify() }.joinToString(" ", prefix = if (asParagraph) "<p>" else "")) + .body().childNodes().mapNotNull { convertHtmlNode(it) } } + private fun PsiDocTag.contentElements(): List<PsiElement> = + dataElements.mapNotNull { it.takeIf { it.text.isNotBlank() } } + + private fun convertJavadocElements(elements: Iterable<PsiElement>, asParagraph: Boolean = true): List<DocTag> = Parse()(elements, asParagraph) + private fun PsiDocToken.isSharpToken() = tokenType.toString() == "DOC_TAG_VALUE_SHARP_TOKEN" + private fun PsiDocToken.isLeadingAsterisk() = tokenType.toString() == "DOC_COMMENT_LEADING_ASTERISKS" + private fun PsiElement.toDocumentationLink(labelElement: PsiElement? = null) = reference?.resolve()?.let { val dri = DRI.from(it) - val label = labelElement ?: children.firstOrNull { - it is PsiDocToken && it.text.isNotBlank() && !it.isSharpToken() - } ?: this - DocumentationLink(dri, convertJavadocElements(listOfNotNull(label))) - } - - private fun convertInlineDocTag(tag: PsiInlineDocTag) = when (tag.name) { - "link", "linkplain" -> { - tag.referenceElement()?.toDocumentationLink(tag.dataElements.firstIsInstanceOrNull<PsiDocToken>()) + val label = labelElement ?: defaultLabel() + DocumentationLink(dri, convertJavadocElements(listOfNotNull(label), asParagraph = false)) } - "code", "literal" -> { - CodeInline(listOf(Text(tag.text))) - } - "index" -> Index(tag.children.filterIsInstance<PsiDocTagValue>().map { Text(it.text) }) - else -> Text(tag.text) - } private fun PsiDocTag.referenceElement(): PsiElement? = linkElement()?.let { @@ -211,6 +253,10 @@ class JavadocParser( } } + private fun PsiElement.defaultLabel() = children.firstOrNull { + it is PsiDocToken && it.text.isNotBlank() && !it.isSharpToken() + } ?: this + private fun PsiDocTag.linkElement(): PsiElement? = valueElement ?: dataElements.firstOrNull { it !is PsiWhiteSpace } } |