Skip to content

Commit

Permalink
Fix missing space between Markdown elements (#2640)
Browse files Browse the repository at this point in the history
  • Loading branch information
vmishenev committed Aug 30, 2022
1 parent a4bccbf commit c9f1d60
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 23 deletions.
44 changes: 25 additions & 19 deletions plugins/base/src/main/kotlin/parsers/MarkdownParser.kt
Expand Up @@ -195,7 +195,9 @@ open class MarkdownParser(
private fun markdownFileHandler(node: ASTNode) =
DocTagsFromIElementFactory.getInstance(
node.type,
children = node.children.evaluateChildren()
children = node.children
.filterSpacesAndEOL()
.evaluateChildren()
)

private fun autoLinksHandler(node: ASTNode): List<DocTag> {
Expand Down Expand Up @@ -250,17 +252,23 @@ open class MarkdownParser(

private fun tableHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance(
GFMElementTypes.TABLE,
children = node.children.filterTabSeparators().evaluateChildren()
children = node.children
.filter { it.type == GFMElementTypes.ROW || it.type == GFMElementTypes.HEADER }
.evaluateChildren()
)

private fun headerHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance(
GFMElementTypes.HEADER,
children = node.children.filterTabSeparators().evaluateChildren()
children = node.children
.filter { it.type == GFMTokenTypes.CELL }
.evaluateChildren()
)

private fun rowHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance(
GFMElementTypes.ROW,
children = node.children.filterTabSeparators().evaluateChildren()
children = node.children
.filter { it.type == GFMTokenTypes.CELL }
.evaluateChildren()
)

private fun cellHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance(
Expand Down Expand Up @@ -391,6 +399,9 @@ open class MarkdownParser(
private fun List<ASTNode>.filterTabSeparators() =
this.filterNot { it.type == GFMTokenTypes.TABLE_SEPARATOR }

private fun List<ASTNode>.filterSpacesAndEOL() =
this.filterNot { it.type == MarkdownTokenTypes.WHITE_SPACE || it.type == MarkdownTokenTypes.EOL }

private fun List<ASTNode>.evaluateChildren(keepAllFormatting: Boolean = false): List<DocTag> =
this.removeUselessTokens().swapImagesThatShouldBeLinks(keepAllFormatting).mergeLeafASTNodes().flatMap { visitNode(it, keepAllFormatting) }

Expand Down Expand Up @@ -430,9 +441,11 @@ open class MarkdownParser(
MarkdownTokenTypes.HTML_BLOCK_CONTENT
)

private fun ASTNode.isNotLeaf() = this is CompositeASTNode || this.type in notLeafNodes

private fun List<ASTNode>.isNotLeaf(index: Int): Boolean =
if (index in 0..this.lastIndex)
(this[index] is CompositeASTNode) || this[index].type in notLeafNodes
this[index].isNotLeaf()
else
false

Expand All @@ -447,33 +460,26 @@ open class MarkdownParser(
val sIndex = index
while (index < this.lastIndex) {
if (this.isNotLeaf(index + 1) || this[index + 1].startOffset != this[index].endOffset) {
mergedLeafNode(this, index, startOffset, sIndex)?.run {
children += this
}
children += mergedLeafNode(this, index, startOffset, sIndex)
break
}
index++
}
if (index == this.lastIndex) {
mergedLeafNode(this, index, startOffset, sIndex)?.run {
children += this
}
children += mergedLeafNode(this, index, startOffset, sIndex)
}
}
index++
}
return children
}

private fun mergedLeafNode(nodes: List<ASTNode>, index: Int, startOffset: Int, sIndex: Int): LeafASTNode? {
private fun mergedLeafNode(nodes: List<ASTNode>, index: Int, startOffset: Int, sIndex: Int): LeafASTNode {
val endOffset = nodes[index].endOffset
if (text.substring(startOffset, endOffset).transform().trim().isNotEmpty()) {
val type = if (nodes.subList(sIndex, index)
.any { it.type == MarkdownTokenTypes.CODE_LINE }
) MarkdownTokenTypes.CODE_LINE else MarkdownTokenTypes.TEXT
return LeafASTNode(type, startOffset, endOffset)
}
return null
val type = if (nodes.subList(sIndex, index)
.any { it.type == MarkdownTokenTypes.CODE_LINE }
) MarkdownTokenTypes.CODE_LINE else MarkdownTokenTypes.TEXT
return LeafASTNode(type, startOffset, endOffset)
}

private fun String.transform() = this
Expand Down
Expand Up @@ -9,7 +9,6 @@ import org.intellij.markdown.flavours.gfm.GFMTokenTypes
import org.jetbrains.dokka.base.translators.parseWithNormalisedSpaces
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.model.doc.DocTag.Companion.contentTypeParam
import java.lang.NullPointerException

object DocTagsFromIElementFactory {

Expand Down Expand Up @@ -40,7 +39,12 @@ object DocTagsFromIElementFactory {
body.orEmpty(),
children,
params
) else body?.parseWithNormalisedSpaces(renderWhiteCharactersAsSpaces = false).orEmpty()
) else {
// corner case: there are only spaces between two Markdown nodes
val containsOnlySpaces = body?.isNotEmpty() == true && body.all { it.isWhitespace() }
if (containsOnlySpaces) Text(" ", children, params)
else body?.parseWithNormalisedSpaces(renderWhiteCharactersAsSpaces = false).orEmpty()
}
MarkdownTokenTypes.HORIZONTAL_RULE -> HorizontalRule
MarkdownTokenTypes.HARD_LINE_BREAK -> Br
GFMElementTypes.STRIKETHROUGH -> Strikethrough(children, params)
Expand Down
53 changes: 51 additions & 2 deletions plugins/base/src/test/kotlin/markdown/ParserTest.kt
Expand Up @@ -2,16 +2,18 @@ package org.jetbrains.dokka.tests

import markdown.KDocTest
import org.intellij.markdown.MarkdownElementTypes
import org.jetbrains.dokka.base.parsers.MarkdownParser
import org.jetbrains.dokka.model.doc.*
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import kotlin.test.assertEquals
import kotlin.test.assertTrue


class ParserTest : KDocTest() {

private fun parseMarkdownToDocNode(text: String) =
MarkdownParser( { null }, "").parseStringToDocNode(text)

@Test
fun `Simple text`() {
val kdoc = """
Expand Down Expand Up @@ -1519,5 +1521,52 @@ class ParserTest : KDocTest() {
)
executeTest(kdoc, expectedDocumentationNode)
}

@Test
fun `code with backticks`() {
val kdoc = "` `` ` ` ``` `"
val expectedDocumentationNode = DocumentationNode(
listOf(
Description(
CustomDocTag(
listOf(
P(
listOf(
CodeInline(listOf(Text("`` "))),
Text(" "),
CodeInline(listOf(Text("``` "))),
)
)
), name = MarkdownElementTypes.MARKDOWN_FILE.name
)
)
)
)
executeTest(kdoc, expectedDocumentationNode)
}

@Test
fun `should filter spaces in markdown`() {
val markdown = """
| sdsdds f,*()hhh
| dssd hf
|
| sdsdsds sdd
|
|
| eweww
|
|
|
""".trimMargin()
val actualDocumentationNode = parseMarkdownToDocNode(markdown).children
val expectedDocumentationNode = listOf(
P(listOf(Text(" sdsdds f,*()hhh dssd hf"))),
P(listOf(Text(" sdsdsds sdd"))),
P(listOf(Text(" eweww ")))
)
print(expectedDocumentationNode)
assertEquals(actualDocumentationNode, expectedDocumentationNode)
}
}

16 changes: 16 additions & 0 deletions plugins/base/src/test/kotlin/model/CommentTest.kt
Expand Up @@ -251,4 +251,20 @@ class CommentTest : AbstractModelTest("/src/main/kotlin/comment/Test.kt", "comme
}
}
}

@Test
fun `should be space between Markdown nodes`() {
inlineModelTest(
"""
|/**
| * Rotates paths by `amount` **radians** around (`x`, `y`).
| */
|val property = "test"
"""
) {
with((this / "comment" / "property").cast<DProperty>()) {
comments() equals "Rotates paths by amount radians around (x, y).\n"
}
}
}
}

0 comments on commit c9f1d60

Please sign in to comment.