From 42c0320e0c5f2d79a5438558bb87d4668aa4c3cc Mon Sep 17 00:00:00 2001 From: vmishenev Date: Wed, 11 Aug 2021 17:26:43 +0300 Subject: Handling `@code` tag (#2059) --- .../translators/psi/parsers/JavadocParser.kt | 85 ++++++++++++---------- .../src/test/kotlin/parsers/JavadocParserTest.kt | 62 +++++++++++++++- 2 files changed, 106 insertions(+), 41 deletions(-) (limited to 'plugins') diff --git a/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocParser.kt b/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocParser.kt index ce022dd7..9cb362cb 100644 --- a/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocParser.kt +++ b/plugins/base/src/main/kotlin/translators/psi/parsers/JavadocParser.kt @@ -8,12 +8,10 @@ import com.intellij.psi.impl.source.tree.LazyParseablePsiElement import com.intellij.psi.impl.source.tree.LeafPsiElement import com.intellij.psi.javadoc.* import org.intellij.markdown.MarkdownElementTypes -import org.intellij.markdown.lexer.Compat.forEachCodePoint import org.jetbrains.dokka.analysis.DokkaResolutionFacade import org.jetbrains.dokka.analysis.from import org.jetbrains.dokka.base.parsers.MarkdownParser import org.jetbrains.dokka.base.translators.parseHtmlEncodedWithNormalisedSpaces -import org.jetbrains.dokka.base.translators.parseWithNormalisedSpaces import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.model.doc.* import org.jetbrains.dokka.utilities.DokkaLogger @@ -24,12 +22,11 @@ import org.jetbrains.kotlin.idea.util.CommentSaver.Companion.tokenType import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespace import org.jetbrains.kotlin.psi.psiUtil.siblings import org.jsoup.Jsoup -import org.jsoup.internal.StringUtil import org.jsoup.nodes.Element -import org.jsoup.nodes.Entities import org.jsoup.nodes.Node import org.jsoup.nodes.TextNode import java.util.* +import org.jetbrains.dokka.utilities.htmlEscape interface JavaDocumentationParser { fun parseDocumentation(element: PsiNamedElement): DocumentationNode @@ -251,32 +248,7 @@ class JavadocParser( is PsiInlineDocTag -> convertInlineDocTag(this, state.currentJavadocTag, context) is PsiDocParamRef -> toDocumentationLinkString() is PsiDocTagValue, - is LeafPsiElement -> { - if (isInsidePre) { - /* - For values in the
 tag we try to keep formatting, so only the leading space is trimmed,
-                        since it is there because it separates this line from the leading asterisk
-                         */
-                        text.let {
-                            if ((prevSibling as? PsiDocToken)?.isLeadingAsterisk() == true && it.firstOrNull() == ' ')
-                                it.drop(1) else it
-                        }.let {
-                            if ((nextSibling as? PsiDocToken)?.isLeadingAsterisk() == true) it.dropLastWhile { it == ' ' } else it
-                        }
-                    } else {
-                        /*
-                        Outside of the 
 we would like to trim everything from the start and end of a line since
-                        javadoc doesn't care about it.
-                         */
-                        text.let {
-                            if ((prevSibling as? PsiDocToken)?.isLeadingAsterisk() == true && text.isNotBlank() && state.previousElement !is PsiInlineDocTag) it?.trimStart() else it
-                        }?.let {
-                            if ((nextSibling as? PsiDocToken)?.isLeadingAsterisk() == true && text.isNotBlank()) it.trimEnd() else it
-                        }?.let {
-                            if (shouldHaveSpaceAtTheEnd()) "$it " else it
-                        }
-                    }
-                }
+                is LeafPsiElement -> stringifyElementAsText(isInsidePre, state.previousElement)
                 else -> null
             }
             val previousElement = if (text.trim() == "") state.previousElement else this
@@ -289,6 +261,31 @@ class JavadocParser(
             )
         }
 
+        private fun PsiElement.stringifyElementAsText(keepFormatting: Boolean, previousElement: PsiElement? = null) =  if (keepFormatting) {
+            /*
+            For values in the 
 tag we try to keep formatting, so only the leading space is trimmed,
+            since it is there because it separates this line from the leading asterisk
+             */
+            text.let {
+                if (((prevSibling as? PsiDocToken)?.isLeadingAsterisk() == true || (prevSibling as? PsiDocToken)?.isTagName() == true ) && it.firstOrNull() == ' ')
+                    it.drop(1) else it
+            }.let {
+                if ((nextSibling as? PsiDocToken)?.isLeadingAsterisk() == true) it.dropLastWhile { it == ' ' } else it
+            }
+        } else {
+            /*
+            Outside of the 
 we would like to trim everything from the start and end of a line since
+            javadoc doesn't care about it.
+             */
+            text.let {
+                if ((prevSibling as? PsiDocToken)?.isLeadingAsterisk() == true && text.isNotBlank() && previousElement !is PsiInlineDocTag) it?.trimStart() else it
+            }?.let {
+                if ((nextSibling as? PsiDocToken)?.isLeadingAsterisk() == true && text.isNotBlank()) it.trimEnd() else it
+            }?.let {
+                if (shouldHaveSpaceAtTheEnd()) "$it " else it
+            }
+        }
+
         /**
          * We would like to know if we need to have a space after a this tag
          *
@@ -338,7 +335,8 @@ class JavadocParser(
             when (tag.name) {
                 "link", "linkplain" -> tag.referenceElement()
                     ?.toDocumentationLinkString(tag.dataElements.filterIsInstance())
-                "code", "literal" -> "${tag.text}"
+                "code", "literal" -> "${tag.dataElements.joinToString("") { it.stringifyElementAsText(keepFormatting = true)
+                    .toString() }.htmlEscape()}"
                 "index" -> "${tag.children.filterIsInstance().joinToString { it.text }}"
                 "inheritDoc" -> inheritDocResolver.resolveFromContext(context)
                     ?.fold(ParsingResult(javadocTag)) { result, e ->
@@ -359,14 +357,15 @@ class JavadocParser(
             }
         }
 
-        private fun createBlock(element: Element, insidePre: Boolean = false): List {
+        private fun createBlock(element: Element, keepFormatting: Boolean = false): List {
+            val tagName = element.tagName()
             val children = element.childNodes()
-                .flatMap { convertHtmlNode(it, insidePre = insidePre || element.tagName() == "pre") }
+                .flatMap { convertHtmlNode(it, keepFormatting = keepFormatting || tagName == "pre" || tagName == "code") }
 
             fun ifChildrenPresent(operation: () -> DocTag): List {
                 return if (children.isNotEmpty()) listOf(operation()) else emptyList()
             }
-            return when (element.tagName()) {
+            return when (tagName) {
                 "blockquote" -> ifChildrenPresent { BlockQuote(children) }
                 "p" -> ifChildrenPresent { P(children) }
                 "b" -> ifChildrenPresent { B(children) }
@@ -374,9 +373,13 @@ class JavadocParser(
                 "index" -> listOf(Index(children))
                 "i" -> ifChildrenPresent { I(children) }
                 "em" -> listOf(Em(children))
-                "code" -> ifChildrenPresent { if(insidePre) CodeBlock(children) else CodeInline(children) }
-                "pre" -> if(children.size == 1 && children.first() is CodeInline) {
-                    listOf(CodeBlock(children.first().children))
+                "code" -> ifChildrenPresent { if(keepFormatting) CodeBlock(children) else CodeInline(children) }
+                "pre" -> if(children.size == 1) {
+                    when(children.first()) {
+                        is CodeInline -> listOf(CodeBlock(children.first().children))
+                        is CodeBlock -> listOf(children.first())
+                        else -> listOf(Pre(children))
+                    }
                 } else {
                     listOf(Pre(children))
                 }
@@ -405,13 +408,13 @@ class JavadocParser(
             }
         }
 
-        private fun convertHtmlNode(node: Node, insidePre: Boolean = false): List = when (node) {
-            is TextNode -> (if (insidePre) {
+        private fun convertHtmlNode(node: Node, keepFormatting: Boolean = false): List = when (node) {
+            is TextNode -> (if (keepFormatting) {
                 node.wholeText.takeIf { it.isNotBlank() }?.let { listOf(Text(body = it)) }
             } else {
                 node.wholeText.parseHtmlEncodedWithNormalisedSpaces(renderWhiteCharactersAsSpaces = true)
             }).orEmpty()
-            is Element -> createBlock(node)
+            is Element -> createBlock(node, keepFormatting)
             else -> emptyList()
         }
 
@@ -438,6 +441,8 @@ class JavadocParser(
 
     private fun PsiDocToken.isSharpToken() = tokenType == JavaDocTokenType.DOC_TAG_VALUE_SHARP_TOKEN
 
+    private fun PsiDocToken.isTagName() = tokenType == JavaDocTokenType.DOC_TAG_NAME
+
     private fun PsiDocToken.isLeadingAsterisk() = tokenType == JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS
 
     private fun PsiElement.toDocumentationLink(labelElement: PsiElement? = null, context: CommentResolutionContext) =
diff --git a/plugins/base/src/test/kotlin/parsers/JavadocParserTest.kt b/plugins/base/src/test/kotlin/parsers/JavadocParserTest.kt
index a6a1413c..d6fffee3 100644
--- a/plugins/base/src/test/kotlin/parsers/JavadocParserTest.kt
+++ b/plugins/base/src/test/kotlin/parsers/JavadocParserTest.kt
@@ -1,14 +1,27 @@
 package parsers
 
+import com.jetbrains.rd.util.first
 import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
 import org.jetbrains.dokka.model.DEnum
 import org.jetbrains.dokka.model.DModule
+import org.jetbrains.dokka.model.doc.CodeBlock
+import org.jetbrains.dokka.model.doc.CodeInline
+import org.jetbrains.dokka.model.doc.Text
 import org.junit.jupiter.api.Assertions.assertEquals
 import org.junit.jupiter.api.Test
-import utils.text
+import utils.*
 
 class JavadocParserTest : BaseAbstractTest() {
 
+    private val configuration = dokkaConfiguration {
+        sourceSets {
+            sourceSet {
+                sourceRoots = listOf("src/")
+                analysisPlatform = "jvm"
+            }
+        }
+    }
+
     private fun performJavadocTest(testOperation: (DModule) -> Unit) {
         val configuration = dokkaConfiguration {
             sourceSets {
@@ -47,4 +60,51 @@ class JavadocParserTest : BaseAbstractTest() {
             assertEquals("content being refreshed, which can be a result of invalidation, refresh that may contain content updates, or the initial load.", docs.trimEnd())
         }
     }
+
+    @Test
+    fun `code tag`() {
+        val source = """
+            |/src/main/kotlin/test/Test.java
+            |package example
+            |
+            | /**
+            | * Identifies calls to {@code assertThat}.
+            | *
+            | * {@code
+            | * Set s;
+            | * System.out.println("s1 = " + s);
+            | * }
+            | * 
{@code
+            | * Set s2;
+            | * System.out
+            | *         .println("s2 = " + s2);
+            | * }
+ | * + | */ + | public class Test {} + """.trimIndent() + testInline( + source, + configuration, + ) { + documentablesCreationStage = { modules -> + val docs = modules.first().packages.first().classlikes.single().documentation.first().value + val root = docs.children.first().root + + kotlin.test.assertEquals( + listOf( + Text(body = "Identifies calls to "), + CodeInline(children = listOf(Text(body = "assertThat"))), + Text(body = ". "), + CodeInline(children = listOf(Text(body = "\nSet s;\nSystem.out.println(\"s1 = \" + s);\n"))) + ), + root.children[0].children + ) + kotlin.test.assertEquals( + CodeBlock(children = listOf(Text(body = "\nSet s2;\nSystem.out\n .println(\"s2 = \" + s2);\n"))), + root.children[1] + ) + } + } + } } -- cgit