diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/build.gradle.kts | 2 | ||||
-rw-r--r-- | core/src/main/kotlin/model/doc/DocTag.kt | 2 | ||||
-rw-r--r-- | core/src/main/kotlin/parsers/MarkdownParser.kt | 166 | ||||
-rw-r--r-- | core/src/main/kotlin/parsers/factories/DocNodesFromIElementFactory.kt | 5 | ||||
-rw-r--r-- | core/src/main/kotlin/parsers/factories/DocNodesFromStringFactory.kt | 2 | ||||
-rw-r--r-- | core/src/test/kotlin/markdown/ParserTest.kt | 939 |
6 files changed, 982 insertions, 134 deletions
diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 06610a38..e70791f3 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -16,7 +16,7 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") implementation("org.jsoup:jsoup:1.12.1") implementation("com.google.code.gson:gson:2.8.5") - implementation("org.jetbrains:markdown:0.1.40") + implementation("org.jetbrains:markdown:0.1.41") implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:0.6.10") testImplementation(project(":testApi")) diff --git a/core/src/main/kotlin/model/doc/DocTag.kt b/core/src/main/kotlin/model/doc/DocTag.kt index 0d8b361a..0224634b 100644 --- a/core/src/main/kotlin/model/doc/DocTag.kt +++ b/core/src/main/kotlin/model/doc/DocTag.kt @@ -93,5 +93,5 @@ class DocumentationLink(children: List<DocTag> = emptyList(), params: Map<String override fun equals(other: Any?): Boolean = super.equals(other) && this.dri == (other as DocumentationLink).dri override fun hashCode(): Int = super.hashCode() + dri.hashCode() } -class HorizontalRule : DocTag(emptyList(), emptyMap()) +object HorizontalRule : DocTag(emptyList(), emptyMap()) class CustomDocTag(children: List<DocTag> = emptyList(), params: Map<String, String> = emptyMap()) : DocTag(children, params)
\ No newline at end of file diff --git a/core/src/main/kotlin/parsers/MarkdownParser.kt b/core/src/main/kotlin/parsers/MarkdownParser.kt index 30a507df..ff27dc2e 100644 --- a/core/src/main/kotlin/parsers/MarkdownParser.kt +++ b/core/src/main/kotlin/parsers/MarkdownParser.kt @@ -6,6 +6,7 @@ import org.intellij.markdown.MarkdownElementTypes import org.intellij.markdown.MarkdownTokenTypes import org.intellij.markdown.ast.ASTNode import org.intellij.markdown.ast.CompositeASTNode +import org.intellij.markdown.ast.LeafASTNode import org.intellij.markdown.ast.impl.ListItemCompositeNode import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor import org.jetbrains.dokka.analysis.DokkaResolutionFacade @@ -15,7 +16,6 @@ import org.jetbrains.dokka.utilities.DokkaConsoleLogger import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag -import org.jetbrains.kotlin.kdoc.psi.api.KDoc import org.jetbrains.kotlin.kdoc.psi.impl.KDocImpl import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag @@ -26,12 +26,13 @@ class MarkdownParser ( private val declarationDescriptor: DeclarationDescriptor ) : Parser() { - inner class MarkdownVisitor(val text: String) { + inner class MarkdownVisitor(val text: String, val destinationLinksMap: Map<String, String>) { private fun headersHandler(node: ASTNode): DocTag = DocNodesFromIElementFactory.getInstance( node.type, - visitNode(node.children.find { it.type == MarkdownTokenTypes.ATX_CONTENT }!!).children + visitNode(node.children.find { it.type == MarkdownTokenTypes.ATX_CONTENT } ?: + throw Error("Wrong AST Tree. ATX Header does not contain expected content")).children ) private fun horizontalRulesHandler(node: ASTNode): DocTag = @@ -44,7 +45,8 @@ class MarkdownParser ( ) private fun blockquotesHandler(node: ASTNode): DocTag = - DocNodesFromIElementFactory.getInstance(node.type, children = node.children.drop(1).map { visitNode(it) }) + DocNodesFromIElementFactory.getInstance(node.type, children = node.children + .filterIsInstance<CompositeASTNode>().evaluateChildren()) private fun listsHandler(node: ASTNode): DocTag { @@ -68,7 +70,7 @@ class MarkdownParser ( it.type, children = it .children - .drop(1) + .filterIsInstance<CompositeASTNode>() .evaluateChildren() ) else @@ -77,34 +79,63 @@ class MarkdownParser ( params = if (node.type == MarkdownElementTypes.ORDERED_LIST) { val listNumberNode = node.children.first().children.first() - mapOf("start" to text.substring(listNumberNode.startOffset, listNumberNode.endOffset).dropLast(2)) + mapOf("start" to text.substring(listNumberNode.startOffset, listNumberNode.endOffset).trim().dropLast(1)) } else emptyMap() ) } - private fun linksHandler(node: ASTNode): DocTag { - val linkNode = node.children.find { it.type == MarkdownElementTypes.LINK_LABEL }!! - val link = text.substring(linkNode.startOffset + 1, linkNode.endOffset - 1) - - val dri: DRI? = if (link.startsWith("http") || link.startsWith("www")) { - null - } else { - resolveKDocLink( - resolutionFacade.resolveSession.bindingContext, - resolutionFacade, - declarationDescriptor, - null, - link.split('.') - ).also { if (it.size > 1) DokkaConsoleLogger.warn("Markdown link resolved more than one element: $it") }.firstOrNull()//.single() - ?.let { DRI.from(it) } + private fun resolveDRI(link: String): DRI? = if (link.startsWith("http") || link.startsWith("www")) { + null + } else { + resolveKDocLink( + resolutionFacade.resolveSession.bindingContext, + resolutionFacade, + declarationDescriptor, + null, + link.split('.') + ).also { if (it.size > 1) throw Error("Markdown link resolved more than one element: $it") }.firstOrNull()//.single() + ?.let { DRI.from(it) } + } - } - val href = mapOf("href" to link) - return when (node.type) { - MarkdownElementTypes.FULL_REFERENCE_LINK -> DocNodesFromIElementFactory.getInstance(node.type, params = href, children = node.children.find { it.type == MarkdownElementTypes.LINK_TEXT }!!.children.drop(1).dropLast(1).evaluateChildren(), dri = dri) - else -> DocNodesFromIElementFactory.getInstance(node.type, params = href, children = listOf(visitNode(linkNode)), dri = dri) - } + private fun referenceLinksHandler(node: ASTNode): DocTag { + val linkLabel = node.children.find { it.type == MarkdownElementTypes.LINK_LABEL } ?: + throw Error("Wrong AST Tree. Reference link does not contain expected content") + val linkText = node.children.findLast { it.type == MarkdownElementTypes.LINK_TEXT } ?: linkLabel + + val linkKey = text.substring(linkLabel.startOffset, linkLabel.endOffset) + + val link = destinationLinksMap[linkKey.toLowerCase()] ?: linkKey + + return linksHandler(linkText, link) + } + + private fun inlineLinksHandler(node: ASTNode): DocTag { + val linkText = node.children.find { it.type == MarkdownElementTypes.LINK_TEXT } ?: + throw Error("Wrong AST Tree. Inline link does not contain expected content") + val linkDestination = node.children.find { it.type == MarkdownElementTypes.LINK_DESTINATION } ?: + throw Error("Wrong AST Tree. Inline link does not contain expected content") + val linkTitle = node.children.find { it.type == MarkdownElementTypes.LINK_TITLE } + + val link = text.substring(linkDestination.startOffset, linkDestination.endOffset) + + return linksHandler(linkText, link, linkTitle) + } + + private fun autoLinksHandler(node: ASTNode): DocTag { + val link = text.substring(node.startOffset + 1, node.endOffset - 1) + + return linksHandler(node, link) + } + + private fun linksHandler(linkText: ASTNode, link: String, linkTitle: ASTNode? = null): DocTag { + val dri: DRI? = resolveDRI(link) + val params = if(linkTitle == null) + mapOf("href" to link) + else + mapOf("href" to link, "title" to text.substring(linkTitle.startOffset + 1, linkTitle.endOffset - 1)) + + return DocNodesFromIElementFactory.getInstance(MarkdownElementTypes.INLINE_LINK, params = params, children = linkText.children.drop(1).dropLast(1).evaluateChildren(), dri = dri) } private fun imagesHandler(node: ASTNode): DocTag { @@ -132,8 +163,11 @@ class MarkdownParser ( node.type, children = node .children - .filter { it.type == MarkdownTokenTypes.CODE_FENCE_CONTENT } - .map { visitNode(it) }, + .let { listOf(Text( body = text.substring( + it.find { it.type == MarkdownTokenTypes.CODE_FENCE_CONTENT }?.startOffset ?: 0, + it.findLast { it.type == MarkdownTokenTypes.CODE_FENCE_CONTENT }?.endOffset ?: 0 //TODO: Problem with empty code fence + )) + ) }, params = node .children .find { it.type == MarkdownTokenTypes.FENCE_LANG } @@ -161,7 +195,9 @@ class MarkdownParser ( MarkdownElementTypes.STRONG, MarkdownElementTypes.EMPH -> emphasisHandler(node) MarkdownElementTypes.FULL_REFERENCE_LINK, - MarkdownElementTypes.SHORT_REFERENCE_LINK -> linksHandler(node) + MarkdownElementTypes.SHORT_REFERENCE_LINK -> referenceLinksHandler(node) + MarkdownElementTypes.INLINE_LINK -> inlineLinksHandler(node) + MarkdownElementTypes.AUTOLINK -> autoLinksHandler(node) MarkdownElementTypes.BLOCK_QUOTE -> blockquotesHandler(node) MarkdownElementTypes.UNORDERED_LIST, MarkdownElementTypes.ORDERED_LIST -> listsHandler(node) @@ -171,20 +207,84 @@ class MarkdownParser ( MarkdownElementTypes.IMAGE -> imagesHandler(node) MarkdownTokenTypes.CODE_FENCE_CONTENT, MarkdownTokenTypes.CODE_LINE, - MarkdownTokenTypes.TEXT -> DocNodesFromIElementFactory.getInstance(MarkdownTokenTypes.TEXT, body = text.substring(node.startOffset, node.endOffset)) + MarkdownTokenTypes.TEXT -> DocNodesFromIElementFactory.getInstance( + MarkdownTokenTypes.TEXT, + body = text + .substring(node.startOffset, node.endOffset).transform() + ) + MarkdownElementTypes.MARKDOWN_FILE -> if(node.children.size == 1) visitNode(node.children.first()) else defaultHandler(node) else -> defaultHandler(node) } private fun List<ASTNode>.evaluateChildren(): List<DocTag> = - this.filter { it is CompositeASTNode || it.type == MarkdownTokenTypes.TEXT }.map { visitNode(it) } + this.removeUselessTokens().mergeLeafASTNodes().map { visitNode(it) } + + private fun List<ASTNode>.removeUselessTokens(): List<ASTNode> = + this.filterIndexed { index, _ -> + !(this[index].type == MarkdownTokenTypes.EOL && + this.isLeaf(index - 1) && this.getOrNull(index - 1)?.type !in leafNodes && + this.isLeaf(index + 1) && this.getOrNull(index + 1)?.type !in leafNodes) && + this[index].type != MarkdownElementTypes.LINK_DEFINITION + } + + private val notLeafNodes = listOf(MarkdownTokenTypes.HORIZONTAL_RULE) + private val leafNodes = listOf(MarkdownElementTypes.STRONG, MarkdownElementTypes.EMPH) + + private fun List<ASTNode>.isLeaf(index: Int): Boolean = + if(index in 0..this.lastIndex) + (this[index] is CompositeASTNode)|| this[index].type in notLeafNodes + else + false + + private fun List<ASTNode>.mergeLeafASTNodes(): List<ASTNode> { + val children: MutableList<ASTNode> = mutableListOf() + var index = 0 + while(index <= this.lastIndex) { + if(this.isLeaf(index)) { + children += this[index] + } + else { + val startOffset = this[index].startOffset + while(index < this.lastIndex) { + if(this.isLeaf(index + 1)) { + val endOffset = this[index].endOffset + if(text.substring(startOffset, endOffset).transform().isNotEmpty()) + children += LeafASTNode(MarkdownTokenTypes.TEXT, startOffset, endOffset) + break + } + index++ + } + if(index == this.lastIndex) { + val endOffset = this[index].endOffset + if(text.substring(startOffset, endOffset).transform().isNotEmpty()) + children += LeafASTNode(MarkdownTokenTypes.TEXT, startOffset, endOffset) + } + } + index++ + } + return children + } + + private fun String.transform() = this + .replace(Regex("\n\n+"), "") + .replace(Regex("\n>+ "), "\n") } + + private fun getAllDestinationLinks(text: String, node: ASTNode): List<Pair<String, String>> = + node.children + .filter { it.type == MarkdownElementTypes.LINK_DEFINITION } + .map { text.substring(it.children[0].startOffset, it.children[0].endOffset).toLowerCase() to + text.substring(it.children[2].startOffset, it.children[2].endOffset) } + + node.children.filterIsInstance<CompositeASTNode>().flatMap { getAllDestinationLinks(text, it) } + + private fun markdownToDocNode(text: String): DocTag { val flavourDescriptor = CommonMarkFlavourDescriptor() val markdownAstRoot: ASTNode = IntellijMarkdownParser(flavourDescriptor).buildMarkdownTreeFromString(text) - return MarkdownVisitor(text).visitNode(markdownAstRoot) + return MarkdownVisitor(text, getAllDestinationLinks(text, markdownAstRoot).toMap()).visitNode(markdownAstRoot) } override fun parseStringToDocNode(extractedString: String) = markdownToDocNode(extractedString) diff --git a/core/src/main/kotlin/parsers/factories/DocNodesFromIElementFactory.kt b/core/src/main/kotlin/parsers/factories/DocNodesFromIElementFactory.kt index 1a302176..b899679d 100644 --- a/core/src/main/kotlin/parsers/factories/DocNodesFromIElementFactory.kt +++ b/core/src/main/kotlin/parsers/factories/DocNodesFromIElementFactory.kt @@ -11,7 +11,8 @@ object DocNodesFromIElementFactory { fun getInstance(type: IElementType, children: List<DocTag> = emptyList(), params: Map<String, String> = emptyMap(), body: String? = null, dri: DRI? = null) = when(type) { MarkdownElementTypes.SHORT_REFERENCE_LINK, - MarkdownElementTypes.FULL_REFERENCE_LINK -> if(dri == null) A(children, params) else DocumentationLink(children, params, dri) + MarkdownElementTypes.FULL_REFERENCE_LINK, + MarkdownElementTypes.INLINE_LINK -> if(dri == null) A(children, params) else DocumentationLink(children, params, dri) MarkdownElementTypes.STRONG -> B(children, params) MarkdownElementTypes.BLOCK_QUOTE -> BlockQuote(children, params) MarkdownElementTypes.CODE_SPAN, @@ -30,7 +31,7 @@ object DocNodesFromIElementFactory { MarkdownElementTypes.UNORDERED_LIST -> Ul(children, params) MarkdownElementTypes.PARAGRAPH -> P(children, params) MarkdownTokenTypes.TEXT -> Text(children, params, body ?: throw NullPointerException("Text body should be at least empty string passed to DocNodes factory!")) - MarkdownTokenTypes.HORIZONTAL_RULE -> HorizontalRule() + MarkdownTokenTypes.HORIZONTAL_RULE -> HorizontalRule else -> CustomDocTag(children, params) } }
\ No newline at end of file diff --git a/core/src/main/kotlin/parsers/factories/DocNodesFromStringFactory.kt b/core/src/main/kotlin/parsers/factories/DocNodesFromStringFactory.kt index 6090cdb4..dc74ecc1 100644 --- a/core/src/main/kotlin/parsers/factories/DocNodesFromStringFactory.kt +++ b/core/src/main/kotlin/parsers/factories/DocNodesFromStringFactory.kt @@ -70,7 +70,7 @@ object DocNodesFromStringFactory { "ul" -> Ul(children, params) "var" -> Var(children, params) "documentationlink" -> DocumentationLink(children, params, dri ?: throw NullPointerException("DRI cannot be passed null while constructing documentation link!")) - "hr" -> HorizontalRule() + "hr" -> HorizontalRule else -> CustomDocTag(children, params) } }
\ No newline at end of file diff --git a/core/src/test/kotlin/markdown/ParserTest.kt b/core/src/test/kotlin/markdown/ParserTest.kt index e5944e17..77ccd769 100644 --- a/core/src/test/kotlin/markdown/ParserTest.kt +++ b/core/src/test/kotlin/markdown/ParserTest.kt @@ -1,154 +1,901 @@ package org.jetbrains.dokka.tests -import org.junit.Test -//import org.jetbrains.dokkatoTestString -//import org.jetbrains.dokka.parseMarkdown +import org.jetbrains.dokka.model.doc.* import org.junit.Ignore +import org.junit.Test +import testApi.testRunner.AbstractKDocTest + + +class ParserTest : AbstractKDocTest() { -@Ignore public class ParserTest { - fun runTestFor(text : String) { - println("MD: ---") - println(text) -// val markdownTree = parseMarkdown(text) - println("AST: ---") -// println(markdownTree.toTestString()) - println() + @Test fun `Simple text`() { + val kdoc = """ + | This is simple test of string + | Next line + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf(Text(body = "This is simple test of string\nNext line"))) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) } - @Test fun text() { - runTestFor("text") + @Test fun `Text with Bold and Emphasis decorators`() { + val kdoc = """ + | This is **simple** test of _string_ + | Next **_line_** + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + Text(body = "This is "), + B(listOf(Text(body = "simple"))), + Text(body = " test of "), + I(listOf(Text(body = "string"))), + Text(body = "\nNext "), + B(listOf(I(listOf(Text(body = "line"))))) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) } - @Test fun textWithSpaces() { - runTestFor("text and string") + @Test fun `Text with Colon`() { + val kdoc = """ + | This is simple text with: colon! + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf(Text(body = "This is simple text with: colon!"))) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) } - @Test fun textWithColon() { - runTestFor("text and string: cool!") + @Test fun `Multilined text`() { + val kdoc = """ + | Text + | and + | String + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf(Text(body = "Text\nand\nString"))) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) } - @Test fun link() { - runTestFor("text [links]") + @Test fun `Paragraphs`() { + val kdoc = """ + | Paragraph number + | one + | + | Paragraph + | number two + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + P(listOf(Text(body = "Paragraph number\none"))), + P(listOf(Text(body = "Paragraph\nnumber two"))) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) } - @Test fun linkWithHref() { - runTestFor("text [links](http://google.com)") + @Test fun `Emphasis with star`() { + val kdoc = "*text*" + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf(I(listOf(Text(body = "text"))))) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) } - @Test fun multiline() { - runTestFor( - """ -text -and -string -""") + @Test fun `Underscores that are not Emphasis`() { + val kdoc = "text_with_underscores" + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf(Text(body = "text_with_underscores"))) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) } - @Test fun para() { - runTestFor( - """ -paragraph number -one + @Test fun `Emphasis with underscores`() { + val kdoc = "_text_" + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf(I(listOf(Text(body = "text"))))) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } -paragraph -number two -""") + @Test fun `Embedded star`() { + val kdoc = "Embedded*Star" + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf(Text(body = "Embedded*Star"))) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) } - @Test fun bulletList() { - runTestFor( - """* list item 1 -* list item 2 -""") + + @Test fun `Unordered list`() { + val kdoc = """ + | * list item 1 + | * list item 2 + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + Ul( + listOf( + Li(listOf(P(listOf(Text(body = "list item 1"))))), + Li(listOf(P(listOf(Text(body = "list item 2"))))) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) } - @Test fun bulletListWithLines() { - runTestFor( - """ -* list item 1 - continue 1 -* list item 2 - continue 2 - """) + @Test fun `Unordered list with multilines`() { + val kdoc = """ + | * list item 1 + | continue 1 + | * list item 2 + | continue 2 + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + Ul( + listOf( + Li(listOf(P(listOf(Text(body = "list item 1\ncontinue 1"))))), + Li(listOf(P(listOf(Text(body = "list item 2\ncontinue 2"))))) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) } - @Test fun bulletListStrong() { - runTestFor( - """ -* list *item* 1 - continue 1 -* list *item* 2 - continue 2 - """) + @Test fun `Unordered list with Bold`() { + val kdoc = """ + | * list **item** 1 + | continue 1 + | * list __item__ 2 + | continue 2 + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + Ul(listOf( + Li(listOf(P(listOf( + Text(body = "list "), + B(listOf(Text(body = "item"))), + Text(body = " 1\ncontinue 1") + )))), + Li(listOf(P(listOf( + Text(body = "list "), + B(listOf(Text(body = "item"))), + Text(body = " 2\ncontinue 2") + )))) + )) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) } - @Test fun emph() { - runTestFor("*text*") + @Test fun `Unordered list with nested bullets`() { + val kdoc = """ + | * Outer first + | Outer next line + | * Outer second + | - Middle first + | Middle next line + | - Middle second + | + Inner first + | Inner next line + | - Middle third + | * Outer third + | + | New paragraph""".trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf( + Ul(listOf( + Li(listOf(P(listOf(Text(body = "Outer first\nOuter next line"))))), + Li(listOf(P(listOf(Text(body = "Outer second"))))), + Ul(listOf( + Li(listOf(P(listOf(Text(body = "Middle first\nMiddle next line"))))), + Li(listOf(P(listOf(Text(body = "Middle second"))))), + Ul(listOf( + Li(listOf(P(listOf(Text(body = "Inner first\nInner next line"))))) + )), + Li(listOf(P(listOf(Text(body = "Middle third"))))) + )), + Li(listOf(P(listOf(Text(body = "Outer third"))))) + )), + P(listOf(Text(body = "New paragraph"))) + )) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) } - @Test fun underscoresNoEmph() { - runTestFor("text_with_underscores") + @Test fun `Ordered list`() { + val kdoc = """ + | 1. list item 1 + | 2. list item 2 + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + Ol( + listOf( + Li(listOf(P(listOf(Text(body = "list item 1"))))), + Li(listOf(P(listOf(Text(body = "list item 2"))))) + ), + mapOf("start" to "1") + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) } - @Test fun emphUnderscores() { - runTestFor("_text_") + + @Test fun `Ordered list beginning from other number`() { + val kdoc = """ + | 9. list item 1 + | 12. list item 2 + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + Ol( + listOf( + Li(listOf(P(listOf(Text(body = "list item 1"))))), + Li(listOf(P(listOf(Text(body = "list item 2"))))) + ), + mapOf("start" to "9") + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) } - @Test fun singleStar() { - runTestFor("Embedded*Star") + @Test fun `Ordered list with multilines`() { + val kdoc = """ + | 2. list item 1 + | continue 1 + | 3. list item 2 + | continue 2 + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + Ol( + listOf( + Li(listOf(P(listOf(Text(body = "list item 1\ncontinue 1"))))), + Li(listOf(P(listOf(Text(body = "list item 2\ncontinue 2"))))) + ), + mapOf("start" to "2") + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) } - @Test fun directive() { - runTestFor("A text \${code with.another.value} with directive") + @Test fun `Ordered list with Bold`() { + val kdoc = """ + | 1. list **item** 1 + | continue 1 + | 2. list __item__ 2 + | continue 2 + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + Ol(listOf( + Li(listOf(P(listOf( + Text(body = "list "), + B(listOf(Text(body = "item"))), + Text(body = " 1\ncontinue 1") + )))), + Li(listOf(P(listOf( + Text(body = "list "), + B(listOf(Text(body = "item"))), + Text(body = " 2\ncontinue 2") + )))) + ), + mapOf("start" to "1") + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) } - @Test fun emphAndEmptySection() { - runTestFor("*text*\n\$sec:\n") + @Test fun `Ordered list with nested bullets`() { + val kdoc = """ + | 1. Outer first + | Outer next line + | 2. Outer second + | 1. Middle first + | Middle next line + | 2. Middle second + | 1. Inner first + | Inner next line + | 5. Middle third + | 4. Outer third + | + | New paragraph""".trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf( + Ol(listOf( + Li(listOf(P(listOf(Text(body = "Outer first\nOuter next line"))))), + Li(listOf(P(listOf(Text(body = "Outer second"))))), + Ol(listOf( + Li(listOf(P(listOf(Text(body = "Middle first\nMiddle next line"))))), + Li(listOf(P(listOf(Text(body = "Middle second"))))), + Ol(listOf( + Li(listOf(P(listOf(Text(body = "Inner first\nInner next line"))))) + ), + mapOf("start" to "1") + ), + Li(listOf(P(listOf(Text(body = "Middle third"))))) + ), + mapOf("start" to "1") + ), + Li(listOf(P(listOf(Text(body = "Outer third"))))) + ), + mapOf("start" to "1") + ), + P(listOf(Text(body = "New paragraph"))) + )) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) } - @Test fun emphAndSection() { - runTestFor("*text*\n\$sec: some text\n") + @Test fun `Ordered nested in Unordered nested in Ordered list`() { + val kdoc = """ + | 1. Outer first + | Outer next line + | 2. Outer second + | + Middle first + | Middle next line + | + Middle second + | 1. Inner first + | Inner next line + | + Middle third + | 4. Outer third + | + | New paragraph""".trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf( + Ol(listOf( + Li(listOf(P(listOf(Text(body = "Outer first\nOuter next line"))))), + Li(listOf(P(listOf(Text(body = "Outer second"))))), + Ul(listOf( + Li(listOf(P(listOf(Text(body = "Middle first\nMiddle next line"))))), + Li(listOf(P(listOf(Text(body = "Middle second"))))), + Ol(listOf( + Li(listOf(P(listOf(Text(body = "Inner first\nInner next line"))))) + ), + mapOf("start" to "1") + ), + Li(listOf(P(listOf(Text(body = "Middle third"))))) + )), + Li(listOf(P(listOf(Text(body = "Outer third"))))) + ), + mapOf("start" to "1") + ), + P(listOf(Text(body = "New paragraph"))) + )) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) } - @Test fun emphAndBracedSection() { - runTestFor("Text *bold* text \n\${sec}: some text") + @Test fun `Header and two paragraphs`() { + val kdoc = """ + | # Header 1 + | Following text + | + | New paragraph + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf( + H1(listOf(Text(body = "Header 1"))), + P(listOf(Text(body = "Following text"))), + P(listOf(Text(body = "New paragraph"))) + )) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) } - @Test fun section() { - runTestFor( - "Plain text \n\$one: Summary \n\${two}: Description with *emphasis* \n\${An example of a section}: Example") + @Ignore //TODO: ATX_2 to ATX_6 and sometimes ATX_1 from jetbrains parser consumes white space. Need to handle it in their library + @Test fun `All headers`() { + val kdoc = """ + | # Header 1 + | Text 1 + | ## Header 2 + | Text 2 + | ### Header 3 + | Text 3 + | #### Header 4 + | Text 4 + | ##### Header 5 + | Text 5 + | ###### Header 6 + | Text 6 + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf( + H1(listOf(Text(body = "Header 1"))), + P(listOf(Text(body = "Text 1"))), + H2(listOf(Text(body = "Header 2"))), + P(listOf(Text(body = "Text 2"))), + H3(listOf(Text(body = "Header 3"))), + P(listOf(Text(body = "Text 3"))), + H4(listOf(Text(body = "Header 4"))), + P(listOf(Text(body = "Text 4"))), + H5(listOf(Text(body = "Header 5"))), + P(listOf(Text(body = "Text 5"))), + H6(listOf(Text(body = "Header 6"))), + P(listOf(Text(body = "Text 6"))) + )) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) } - @Test fun anonymousSection() { - runTestFor("Summary\n\nDescription\n") + @Test fun `Bold New Line Bold`() { + val kdoc = """ + | **line 1** + | **line 2** + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf( + B(listOf(Text(body = "line 1"))), + Text(body = "\n"), + B(listOf(Text(body = "line 2"))) + )) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) } - @Test fun specialSection() { - runTestFor( - "Plain text \n\$\$summary: Summary \n\${\$description}: Description \n\${\$An example of a section}: Example") + @Test fun `Horizontal rule`() { + val kdoc = """ + | *** + | text 1 + | ___ + | text 2 + | *** + | text 3 + | ___ + | text 4 + | *** + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf( + HorizontalRule, + P(listOf(Text(body = "text 1"))), + HorizontalRule, + P(listOf(Text(body = "text 2"))), + HorizontalRule, + P(listOf(Text(body = "text 3"))), + HorizontalRule, + P(listOf(Text(body = "text 4"))), + HorizontalRule + )) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) } - @Test fun emptySection() { - runTestFor( - "Plain text \n\$summary:") + @Test fun `Blockquote`() { + val kdoc = """ + | > Blockquotes are very handy in email to emulate reply text. + | > This line is part of the same quote. + | + | Quote break. + | + | > Quote + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf( + BlockQuote(listOf( + P(listOf(Text(body = "Blockquotes are very handy in email to emulate reply text.\nThis line is part of the same quote."))) + )), + P(listOf(Text(body = "Quote break."))), + BlockQuote(listOf( + P(listOf(Text(body = "Quote"))) + )) + )) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) } - val b = "$" - @Test fun pair() { - runTestFor( - """Represents a generic pair of two values. -There is no meaning attached to values in this class, it can be used for any purpose. -Pair exhibits value semantics, i.e. two pairs are equal if both components are equal. + @Test fun `Blockquote nested`() { + val kdoc = """ + | > text 1 + | > text 2 + | >> text 3 + | >> text 4 + | > + | > text 5 + | + | Quote break. + | + | > Quote + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf( + BlockQuote(listOf( + P(listOf(Text(body = "text 1\ntext 2"))), + BlockQuote(listOf( + P(listOf(Text(body = "text 3\ntext 4"))) + )), + P(listOf(Text(body = "text 5"))) + )), + P(listOf(Text(body = "Quote break."))), + BlockQuote(listOf( + P(listOf(Text(body = "Quote"))) + )) + )) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Ignore //TODO: Again ATX_1 consumes white space + @Test fun `Blockquote nested with fancy text enhancement`() { + val kdoc = """ + | > text **1** + | > text 2 + | >> # text 3 + | >> * text 4 + | >> * text 5 + | > + | > text 6 + | + | Quote break. + | + | > Quote + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf( + BlockQuote(listOf( + P(listOf( + Text(body = "text "), + B(listOf(Text(body = "1"))), + Text(body = "\ntext 2") + )), + BlockQuote(listOf( + H1(listOf(Text(body = "text 3"))), + Ul(listOf( + Li(listOf(P(listOf(Text(body = "text 4"))))), + Ul(listOf( + Li(listOf(P(listOf(Text(body = "text 5"))))) + ) + ))) + )), + P(listOf(Text(body = "text 6"))) + )), + P(listOf(Text(body = "Quote break."))), + BlockQuote(listOf( + P(listOf(Text(body = "Quote"))) + )) + )) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } -An example of decomposing it into values: -${b}{code test.tuples.PairTest.pairMultiAssignment} + @Test fun `Simple Code Block`() { + val kdoc = """ + | `Some code` + | Sample text + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf( + Code(listOf(Text(body = "Some code"))), + Text(body = "\nSample text") + )) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } -${b}constructor: Creates new instance of [Pair] -${b}first: First value -${b}second: Second value"""" - ) + @Test fun `Multilined Code Block`() { + val kdoc = """ + | ```kotlin + | val x: Int = 0 + | val y: String = "Text" + | + | val z: Boolean = true + | for(i in 0..10) { + | println(i) + | } + | ``` + | Sample text + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf( + Code( + listOf( + Text(body = "val x: Int = 0\nval y: String = \"Text\"\n\n val z: Boolean = true\n" + + "for(i in 0..10) {\n println(i)\n}") + ), + mapOf("lang" to "kotlin") + ), + P(listOf(Text(body = "Sample text"))) + )) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) } + + @Test fun `Inline link`() { + val kdoc = """ + | [I'm an inline-style link](https://www.google.com) + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf(A( + listOf(Text(body = "I'm an inline-style link")), + mapOf("href" to "https://www.google.com") + ))) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test fun `Inline link with title`() { + val kdoc = """ + | [I'm an inline-style link with title](https://www.google.com "Google's Homepage") + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf(A( + listOf(Text(body = "I'm an inline-style link with title")), + mapOf("href" to "https://www.google.com", "title" to "Google's Homepage") + ))) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test fun `Full reference link`() { + val kdoc = """ + | [I'm a reference-style link][Arbitrary case-insensitive reference text] + | + | [arbitrary case-insensitive reference text]: https://www.mozilla.org + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf(P(listOf(A( + listOf(Text(body = "I'm a reference-style link")), + mapOf("href" to "https://www.mozilla.org") + ))))) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test fun `Full reference link with number`() { + val kdoc = """ + | [You can use numbers for reference-style link definitions][1] + | + | [1]: http://slashdot.org + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf(P(listOf(A( + listOf(Text(body = "You can use numbers for reference-style link definitions")), + mapOf("href" to "http://slashdot.org") + ))))) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test fun `Short reference link`() { + val kdoc = """ + | Or leave it empty and use the [link text itself]. + | + | [link text itself]: http://www.reddit.com + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf(P(listOf( + Text(body = "Or leave it empty and use the "), + A( + listOf(Text(body = "link text itself")), + mapOf("href" to "http://www.reddit.com") + ), + Text(body = ".") + )))) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test fun `Autolink`() { + val kdoc = """ + | URLs and URLs in angle brackets will automatically get turned into links. + | http://www.example.com or <http://www.example.com> and sometimes + | example.com (but not on Github, for example). + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf( + Text(body = "URLs and URLs in angle brackets will automatically get turned into links.\nhttp://www.example.com or "), + A( + listOf(Text(body = "http://www.example.com")), + mapOf("href" to "http://www.example.com") + ), + Text(body = " and sometimes\nexample.com (but not on Github, for example).") + )) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test fun `Various links`() { + val kdoc = """ + | [I'm an inline-style link](https://www.google.com) + | + | [I'm an inline-style link with title](https://www.google.com "Google's Homepage") + | + | [I'm a reference-style link][Arbitrary case-insensitive reference text] + | + | [You can use numbers for reference-style link definitions][1] + | + | Or leave it empty and use the [link text itself]. + | + | URLs and URLs in angle brackets will automatically get turned into links. + | http://www.example.com or <http://www.example.com> and sometimes + | example.com (but not on Github, for example). + | + | Some text to show that the reference links can follow later. + | + | [arbitrary case-insensitive reference text]: https://www.mozilla.org + | [1]: http://slashdot.org + | [link text itself]: http://www.reddit.com + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf( + P(listOf(A( + listOf(Text(body = "I'm an inline-style link")), + mapOf("href" to "https://www.google.com") + ))), + P(listOf(A( + listOf(Text(body = "I'm an inline-style link with title")), + mapOf("href" to "https://www.google.com", "title" to "Google's Homepage") + ))), + P(listOf(A( + listOf(Text(body = "I'm a reference-style link")), + mapOf("href" to "https://www.mozilla.org") + ))), + P(listOf(A( + listOf(Text(body = "You can use numbers for reference-style link definitions")), + mapOf("href" to "http://slashdot.org") + ))), + P(listOf( + Text(body = "Or leave it empty and use the "), + A( + listOf(Text(body = "link text itself")), + mapOf("href" to "http://www.reddit.com") + ), + Text(body = ".") + )), + P(listOf( + Text(body = "URLs and URLs in angle brackets will automatically get turned into links.\nhttp://www.example.com or "), + A( + listOf(Text(body = "http://www.example.com")), + mapOf("href" to "http://www.example.com") + ), + Text(body = " and sometimes\nexample.com (but not on Github, for example).") + )), + P(listOf(Text(body = "Some text to show that the reference links can follow later."))) + )) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } } |