aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVadim Mishenev <vad-mishenev@yandex.ru>2022-04-11 18:22:44 +0300
committerGitHub <noreply@github.com>2022-04-11 18:22:44 +0300
commitc59d6ef872186d185318c2bdcedabf9eaaa7b8ca (patch)
tree0ed30277d90a82beb9be91b296d572fc4560d74f
parent2f0a259a5f07ebe41e9a5510b3684d95bc40b95f (diff)
downloaddokka-c59d6ef872186d185318c2bdcedabf9eaaa7b8ca.tar.gz
dokka-c59d6ef872186d185318c2bdcedabf9eaaa7b8ca.tar.bz2
dokka-c59d6ef872186d185318c2bdcedabf9eaaa7b8ca.zip
Fix using `@` symbol inside code block (#2418)
-rw-r--r--plugins/base/src/main/kotlin/parsers/Parser.kt81
-rw-r--r--plugins/base/src/test/kotlin/markdown/ParserTest.kt2
-rw-r--r--plugins/base/src/test/kotlin/parsers/ParseModuleAndPackageDocumentationFragmentsTest.kt67
3 files changed, 141 insertions, 9 deletions
diff --git a/plugins/base/src/main/kotlin/parsers/Parser.kt b/plugins/base/src/main/kotlin/parsers/Parser.kt
index 960d7a64..af07ec53 100644
--- a/plugins/base/src/main/kotlin/parsers/Parser.kt
+++ b/plugins/base/src/main/kotlin/parsers/Parser.kt
@@ -1,6 +1,8 @@
package org.jetbrains.dokka.base.parsers
import org.jetbrains.dokka.model.doc.*
+import org.jetbrains.dokka.model.doc.Deprecated
+import org.jetbrains.dokka.model.doc.Suppress
abstract class Parser {
@@ -9,7 +11,7 @@ abstract class Parser {
abstract fun preparse(text: String): String
open fun parse(text: String): DocumentationNode =
- DocumentationNode(jkdocToListOfPairs(preparse(text)).map { (tag, content) -> parseTagWithBody(tag, content) })
+ DocumentationNode(extractTagsToListOfPairs(preparse(text)).map { (tag, content) -> parseTagWithBody(tag, content) })
open fun parseTagWithBody(tagName: String, content: String): TagWrapper =
when (tagName) {
@@ -47,12 +49,83 @@ abstract class Parser {
else -> CustomTagWrapper(parseStringToDocNode(content), tagName)
}
- private fun jkdocToListOfPairs(javadoc: String): List<Pair<String, String>> =
- "description $javadoc"
- .split("\n@")
+ /**
+ * KDoc parser from Kotlin compiler relies on a comment asterisk
+ * So there is a mini parser here
+ * TODO: at least to adapt [org.jetbrains.kotlin.kdoc.lexer.KDocLexer] to analyze KDoc without the asterisks and use it here
+ */
+ private fun extractTagsToListOfPairs(text: String): List<Pair<String, String>> =
+ "description $text"
+ .extractKDocSections()
.map { content ->
val contentWithEscapedAts = content.replace("\\@", "@")
val (tag, body) = contentWithEscapedAts.split(" ", limit = 2)
tag to body
}
+
+ /**
+ * Ignore a doc tag inside code spans and blocks
+ * @see org.jetbrains.kotlin.kdoc.psi.impl.KDocSection
+ */
+ private fun CharSequence.extractKDocSections(delimiter: String = "\n@"): List<String> {
+ var countOfBackticks = 0
+ var countOfTildes = 0
+ var countOfBackticksInOpeningFence = 0
+ var countOfTildesInOpeningFence = 0
+
+ var isInCode = false
+ val result = mutableListOf<String>()
+ var rangeStart = 0
+ var rangeEnd = 0
+ var currentOffset = 0
+ while (currentOffset < length) {
+
+ when (get(currentOffset)) {
+ '`' -> {
+ countOfBackticks++
+ countOfTildes = 0
+ }
+ '~' -> {
+ countOfTildes++
+ countOfBackticks = 0
+ }
+ else -> {
+ if (isInCode) {
+ // The closing code fence must be at least as long as the opening fence
+ if(countOfBackticks >= countOfBackticksInOpeningFence
+ || countOfTildes >= countOfTildesInOpeningFence)
+ isInCode = false
+ } else {
+ // as per CommonMark spec, there can be any number of backticks for a code span, not only one or three
+ if (countOfBackticks > 0) {
+ isInCode = true
+ countOfBackticksInOpeningFence = countOfBackticks
+ countOfTildesInOpeningFence = Int.MAX_VALUE
+ }
+ // tildes are only for a code block, not code span
+ if (countOfTildes >= 3) {
+ isInCode = true
+ countOfTildesInOpeningFence = countOfTildes
+ countOfBackticksInOpeningFence = Int.MAX_VALUE
+ }
+ }
+ countOfTildes = 0
+ countOfBackticks = 0
+ }
+ }
+ if (!isInCode && startsWith(delimiter, currentOffset)) {
+ result.add(substring(rangeStart, rangeEnd))
+ currentOffset += delimiter.length
+ rangeStart = currentOffset
+ rangeEnd = currentOffset
+ continue
+ }
+
+ ++rangeEnd
+ ++currentOffset
+ }
+ result.add(substring(rangeStart, rangeEnd))
+ return result
+ }
+
}
diff --git a/plugins/base/src/test/kotlin/markdown/ParserTest.kt b/plugins/base/src/test/kotlin/markdown/ParserTest.kt
index a9e1eee4..3498f73f 100644
--- a/plugins/base/src/test/kotlin/markdown/ParserTest.kt
+++ b/plugins/base/src/test/kotlin/markdown/ParserTest.kt
@@ -951,6 +951,7 @@ class ParserTest : KDocTest() {
fun `Multilined Code Block`() {
val kdoc = """
| ```kotlin
+ | @Suppress("UNUSED_VARIABLE")
| val x: Int = 0
| val y: String = "Text"
|
@@ -968,6 +969,7 @@ class ParserTest : KDocTest() {
listOf(
CodeBlock(
listOf(
+ Text("@Suppress(\"UNUSED_VARIABLE\")"), Br,
Text("val x: Int = 0"), Br,
Text("val y: String = \"Text\""), Br, Br,
Text(" val z: Boolean = true"), Br,
diff --git a/plugins/base/src/test/kotlin/parsers/ParseModuleAndPackageDocumentationFragmentsTest.kt b/plugins/base/src/test/kotlin/parsers/ParseModuleAndPackageDocumentationFragmentsTest.kt
index 4560c532..b6f9307f 100644
--- a/plugins/base/src/test/kotlin/parsers/ParseModuleAndPackageDocumentationFragmentsTest.kt
+++ b/plugins/base/src/test/kotlin/parsers/ParseModuleAndPackageDocumentationFragmentsTest.kt
@@ -1,12 +1,11 @@
package parsers
-import org.jetbrains.dokka.base.parsers.moduleAndPackage.IllegalModuleAndPackageDocumentation
+import org.intellij.markdown.MarkdownElementTypes
+import org.jetbrains.dokka.base.parsers.moduleAndPackage.*
import org.jetbrains.dokka.base.parsers.moduleAndPackage.ModuleAndPackageDocumentation.Classifier.Module
import org.jetbrains.dokka.base.parsers.moduleAndPackage.ModuleAndPackageDocumentation.Classifier.Package
-import org.jetbrains.dokka.base.parsers.moduleAndPackage.ModuleAndPackageDocumentationFile
-import org.jetbrains.dokka.base.parsers.moduleAndPackage.ModuleAndPackageDocumentationFragment
-import org.jetbrains.dokka.base.parsers.moduleAndPackage.ModuleAndPackageDocumentationSource
-import org.jetbrains.dokka.base.parsers.moduleAndPackage.parseModuleAndPackageDocumentationFragments
+import org.jetbrains.dokka.model.doc.*
+import org.jetbrains.dokka.utilities.DokkaLogger
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
@@ -216,6 +215,64 @@ class ParseModuleAndPackageDocumentationFragmentsTest {
)
}
+ @Test
+ fun `at in code block is supported`() {
+ val fragment = parseModuleAndPackageDocumentationFragments(
+ source(
+ """
+ # Module My Module
+ ```
+ @Smth
+ ```
+ @author Smb
+ """.trimIndent()
+ )
+ )
+
+ assertEquals(
+ "```\n" +
+ "@Smth\n" +
+ "```\n" +
+ "@author Smb", fragment.single().documentation,
+ "Expected documentation being available"
+ )
+
+ val parsingContext = ModuleAndPackageDocumentationParsingContext(object : DokkaLogger {
+ override var warningsCount: Int = 0
+ override var errorsCount: Int = 0
+ override fun debug(message: String) {}
+ override fun info(message: String) {}
+ override fun progress(message: String) {}
+ override fun warn(message: String) {}
+ override fun error(message: String) {}
+ })
+ val parsedFragment = parseModuleAndPackageDocumentation(parsingContext, fragment.single())
+ val expectedDocumentationNode = DocumentationNode(
+ listOf(
+ Description(
+ CustomDocTag(
+ listOf(
+ CodeBlock(
+ listOf(
+ Text("@Smth")
+ )
+ )
+ ), name = MarkdownElementTypes.MARKDOWN_FILE.name
+ )
+ ),
+ Author(
+ CustomDocTag(
+ listOf(
+ P(listOf(Text("Smb")))
+ ), name = MarkdownElementTypes.MARKDOWN_FILE.name
+ )
+ )
+ )
+ )
+ assertEquals(
+ expectedDocumentationNode, parsedFragment.documentation
+ )
+ }
private fun source(documentation: String): ModuleAndPackageDocumentationSource =
object : ModuleAndPackageDocumentationSource() {