aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormkondratek <mkondratek@virtuslab.com>2020-09-11 21:38:10 +0200
committerAndrzej Ratajczak <32793002+BarkingBad@users.noreply.github.com>2020-09-29 16:04:51 +0200
commit5a214c157e29145b8828b69060ba5111c7f1e166 (patch)
tree3b9829e139ef7d6f998eb3c78e1b4d1b1fc457d7
parent26415d91fcf32f0c70af4f9cea9500bca8187b0e (diff)
downloaddokka-5a214c157e29145b8828b69060ba5111c7f1e166.tar.gz
dokka-5a214c157e29145b8828b69060ba5111c7f1e166.tar.bz2
dokka-5a214c157e29145b8828b69060ba5111c7f1e166.zip
Add MD parser
-rw-r--r--plugins/base/src/main/kotlin/allModulePage/MultimodulePageCreator.kt2
-rw-r--r--plugins/base/src/main/kotlin/parsers/MarkdownParser.kt811
-rw-r--r--plugins/base/src/main/kotlin/parsers/Parser.kt2
-rw-r--r--plugins/base/src/main/kotlin/parsers/moduleAndPackage/ModuleAndPackageDocumentationParsingContext.kt42
-rw-r--r--plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt20
5 files changed, 449 insertions, 428 deletions
diff --git a/plugins/base/src/main/kotlin/allModulePage/MultimodulePageCreator.kt b/plugins/base/src/main/kotlin/allModulePage/MultimodulePageCreator.kt
index f949ee7a..57c36634 100644
--- a/plugins/base/src/main/kotlin/allModulePage/MultimodulePageCreator.kt
+++ b/plugins/base/src/main/kotlin/allModulePage/MultimodulePageCreator.kt
@@ -3,7 +3,6 @@ package org.jetbrains.dokka.base.allModulePage
import org.jetbrains.dokka.DokkaConfiguration.DokkaModuleDescription
import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
import org.jetbrains.dokka.base.DokkaBase
-import org.jetbrains.dokka.base.parsers.MarkdownParser
import org.jetbrains.dokka.base.parsers.moduleAndPackage.ModuleAndPackageDocumentation.Classifier.Module
import org.jetbrains.dokka.base.parsers.moduleAndPackage.ModuleAndPackageDocumentationParsingContext
import org.jetbrains.dokka.base.parsers.moduleAndPackage.parseModuleAndPackageDocumentation
@@ -27,7 +26,6 @@ class MultimodulePageCreator(
private val logger: DokkaLogger = context.logger
override fun invoke(): RootPageNode {
- val parser = MarkdownParser(logger = logger)
val modules = context.configuration.modules
val commentsConverter = context.plugin(DokkaBase::class)?.querySingle { commentsToContentConverter }
diff --git a/plugins/base/src/main/kotlin/parsers/MarkdownParser.kt b/plugins/base/src/main/kotlin/parsers/MarkdownParser.kt
index d0fd124c..dfb9f8e7 100644
--- a/plugins/base/src/main/kotlin/parsers/MarkdownParser.kt
+++ b/plugins/base/src/main/kotlin/parsers/MarkdownParser.kt
@@ -10,17 +10,10 @@ import org.intellij.markdown.ast.impl.ListItemCompositeNode
import org.intellij.markdown.flavours.gfm.GFMElementTypes
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
import org.intellij.markdown.flavours.gfm.GFMTokenTypes
-import org.jetbrains.dokka.analysis.DokkaResolutionFacade
-import org.jetbrains.dokka.analysis.from
import org.jetbrains.dokka.base.parsers.factories.DocTagsFromIElementFactory
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.model.doc.*
import org.jetbrains.dokka.model.doc.Suppress
-import org.jetbrains.dokka.utilities.DokkaLogger
-import org.jetbrains.kotlin.descriptors.ClassDescriptor
-import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
-import org.jetbrains.kotlin.descriptors.FunctionDescriptor
-import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink
import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag
import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection
import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
@@ -28,485 +21,469 @@ import java.net.MalformedURLException
import java.net.URL
import org.intellij.markdown.parser.MarkdownParser as IntellijMarkdownParser
-class MarkdownParser(
- private val resolutionFacade: DokkaResolutionFacade? = null,
- private val declarationDescriptor: DeclarationDescriptor? = null,
- private val logger: DokkaLogger
+open class MarkdownParser(
+ private val externalDri: (String) -> DRI?
) : Parser() {
- inner class MarkdownVisitor(val text: String, val destinationLinksMap: Map<String, String>) {
-
- private fun headersHandler(node: ASTNode): DocTag =
- DocTagsFromIElementFactory.getInstance(
- node.type,
- visitNode(node.children.find { it.type == MarkdownTokenTypes.ATX_CONTENT }
- ?: throw IllegalStateException("Wrong AST Tree. ATX Header does not contain expected content")).children
- )
-
- private fun horizontalRulesHandler(node: ASTNode): DocTag =
- DocTagsFromIElementFactory.getInstance(MarkdownTokenTypes.HORIZONTAL_RULE)
-
- private fun emphasisHandler(node: ASTNode): DocTag =
- DocTagsFromIElementFactory.getInstance(
- node.type,
- children = node.children.evaluateChildrenWithDroppedEnclosingTokens(1)
- )
+ override fun parseStringToDocNode(extractedString: String): DocTag {
+ val gfmFlavourDescriptor = GFMFlavourDescriptor()
+ val markdownAstRoot: ASTNode =
+ IntellijMarkdownParser(gfmFlavourDescriptor).buildMarkdownTreeFromString(extractedString)
+ destinationLinksMap = getAllDestinationLinks(extractedString, markdownAstRoot).toMap()
+ text = extractedString
+ return visitNode(markdownAstRoot)
+ }
- private fun strongHandler(node: ASTNode): DocTag =
- DocTagsFromIElementFactory.getInstance(
- node.type,
- children = node.children.evaluateChildrenWithDroppedEnclosingTokens(2)
- )
+ override fun preparse(text: String) = text
- private fun List<ASTNode>.evaluateChildrenWithDroppedEnclosingTokens(count: Int) =
- drop(count).dropLast(count).evaluateChildren()
+ private lateinit var destinationLinksMap: Map<String, String>
+ private lateinit var text: String
- private fun blockquotesHandler(node: ASTNode): DocTag =
- DocTagsFromIElementFactory.getInstance(
- node.type, children = node.children
- .filterIsInstance<CompositeASTNode>()
- .evaluateChildren()
- )
+ override fun parse(text: String): DocumentationNode {
+ return DocumentationNode(listOf(Description(parseStringToDocNode(text))))
+ }
- private fun listsHandler(node: ASTNode): DocTag {
-
- val children = node.children.filterIsInstance<ListItemCompositeNode>().flatMap {
- if (it.children.last().type in listOf(
- MarkdownElementTypes.ORDERED_LIST,
- MarkdownElementTypes.UNORDERED_LIST
- )
- ) {
- val nestedList = it.children.last()
- (it.children as MutableList).removeAt(it.children.lastIndex)
- listOf(it, nestedList)
- } else
- listOf(it)
- }
+ private fun headersHandler(node: ASTNode) =
+ DocTagsFromIElementFactory.getInstance(
+ node.type,
+ visitNode(node.children.find { it.type == MarkdownTokenTypes.ATX_CONTENT }
+ ?: throw IllegalStateException("Wrong AST Tree. ATX Header does not contain expected content")).children
+ )
- return DocTagsFromIElementFactory.getInstance(
- node.type,
- children =
- children
- .map {
- if (it.type == MarkdownElementTypes.LIST_ITEM)
- DocTagsFromIElementFactory.getInstance(
- it.type,
- children = it
- .children
- .filterIsInstance<CompositeASTNode>()
- .evaluateChildren()
- )
- else
- visitNode(it)
- },
- params =
- if (node.type == MarkdownElementTypes.ORDERED_LIST) {
- val listNumberNode = node.children.first().children.first()
- mapOf(
- "start" to text.substring(
- listNumberNode.startOffset,
- listNumberNode.endOffset
- ).trim().dropLast(1)
- )
- } else
- emptyMap()
- )
- }
+ private fun horizontalRulesHandler(node: ASTNode) =
+ DocTagsFromIElementFactory.getInstance(MarkdownTokenTypes.HORIZONTAL_RULE)
- private fun resolveDRI(mdLink: String): DRI? =
- mdLink
- .removePrefix("[")
- .removeSuffix("]")
- .let { link ->
- try {
- URL(link)
- null
- } catch (e: MalformedURLException) {
- try {
- if (resolutionFacade != null && declarationDescriptor != null) {
- resolveKDocLink(
- resolutionFacade.resolveSession.bindingContext,
- resolutionFacade,
- declarationDescriptor,
- null,
- link.split('.')
- ).sorted().firstOrNull()?.let { DRI.from(it) }
- } else null
- } catch (e1: IllegalArgumentException) {
- logger.warn("Couldn't resolve link for $mdLink")
- null
- }
- }
- }
+ private fun emphasisHandler(node: ASTNode) =
+ DocTagsFromIElementFactory.getInstance(
+ node.type,
+ children = node.children.evaluateChildrenWithDroppedEnclosingTokens(1)
+ )
- private fun Collection<DeclarationDescriptor>.sorted() = sortedWith(
- compareBy(
- { it is ClassDescriptor },
- { (it as? FunctionDescriptor)?.name },
- { (it as? FunctionDescriptor)?.valueParameters?.size },
- { (it as? FunctionDescriptor)?.valueParameters?.joinToString { it.type.toString() } }
- )
+ private fun strongHandler(node: ASTNode) =
+ DocTagsFromIElementFactory.getInstance(
+ node.type,
+ children = node.children.evaluateChildrenWithDroppedEnclosingTokens(2)
)
- private fun referenceLinksHandler(node: ASTNode): DocTag {
- val linkLabel = node.children.find { it.type == MarkdownElementTypes.LINK_LABEL }
- ?: throw IllegalStateException("Wrong AST Tree. Reference link does not contain expected content")
- val linkText = node.children.findLast { it.type == MarkdownElementTypes.LINK_TEXT } ?: linkLabel
+ private fun List<ASTNode>.evaluateChildrenWithDroppedEnclosingTokens(count: Int) =
+ drop(count).dropLast(count).evaluateChildren()
- val linkKey = text.substring(linkLabel.startOffset, linkLabel.endOffset)
+ private fun blockquotesHandler(node: ASTNode) =
+ DocTagsFromIElementFactory.getInstance(
+ node.type, children = node.children
+ .filterIsInstance<CompositeASTNode>()
+ .evaluateChildren()
+ )
- val link = destinationLinksMap[linkKey.toLowerCase()] ?: linkKey
+ private fun listsHandler(node: ASTNode): DocTag {
- return linksHandler(linkText, link)
+ val children = node.children.filterIsInstance<ListItemCompositeNode>().flatMap {
+ if (it.children.last().type in listOf(
+ MarkdownElementTypes.ORDERED_LIST,
+ MarkdownElementTypes.UNORDERED_LIST
+ )
+ ) {
+ val nestedList = it.children.last()
+ (it.children as MutableList).removeAt(it.children.lastIndex)
+ listOf(it, nestedList)
+ } else
+ listOf(it)
}
- private fun inlineLinksHandler(node: ASTNode): DocTag {
- val linkText = node.children.find { it.type == MarkdownElementTypes.LINK_TEXT }
- ?: throw IllegalStateException("Wrong AST Tree. Inline link does not contain expected content")
- val linkDestination = node.children.find { it.type == MarkdownElementTypes.LINK_DESTINATION }
- ?: throw IllegalStateException("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 DocTagsFromIElementFactory.getInstance(
+ node.type,
+ children =
+ children
+ .map {
+ if (it.type == MarkdownElementTypes.LIST_ITEM)
+ DocTagsFromIElementFactory.getInstance(
+ it.type,
+ children = it
+ .children
+ .filterIsInstance<CompositeASTNode>()
+ .evaluateChildren()
+ )
+ else
+ visitNode(it)
+ },
+ params =
+ if (node.type == MarkdownElementTypes.ORDERED_LIST) {
+ val listNumberNode = node.children.first().children.first()
+ mapOf(
+ "start" to text.substring(
+ listNumberNode.startOffset,
+ listNumberNode.endOffset
+ ).trim().dropLast(1)
+ )
+ } else
+ emptyMap()
+ )
+ }
- return linksHandler(linkText, link, linkTitle)
- }
+ private fun resolveDRI(mdLink: String): DRI? =
+ mdLink
+ .removePrefix("[")
+ .removeSuffix("]")
+ .let { link ->
+ try {
+ URL(link)
+ null
+ } catch (e: MalformedURLException) {
+ externalDri(link)
+ }
+ }
- private fun autoLinksHandler(node: ASTNode): DocTag {
- val link = text.substring(node.startOffset + 1, node.endOffset - 1)
+ 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) }
- return linksHandler(node, link)
- }
- private fun linksHandler(linkText: ASTNode, link: String, linkTitle: ASTNode? = null): DocTag {
- val dri: DRI? = resolveDRI(link)
- val linkTextString =
- if (linkTitle == null) link else text.substring(linkTitle.startOffset + 1, linkTitle.endOffset - 1)
+ private fun referenceLinksHandler(node: ASTNode): DocTag {
+ val linkLabel = node.children.find { it.type == MarkdownElementTypes.LINK_LABEL }
+ ?: throw IllegalStateException("Wrong AST Tree. Reference link does not contain expected content")
+ val linkText = node.children.findLast { it.type == MarkdownElementTypes.LINK_TEXT } ?: linkLabel
- val params = if (linkTitle == null)
- mapOf("href" to link)
- else
- mapOf("href" to link, "title" to linkTextString)
+ val linkKey = text.substring(linkLabel.startOffset, linkLabel.endOffset)
+ val link = destinationLinksMap[linkKey.toLowerCase()] ?: linkKey
- return if (dri == null && !link.isRemoteLink()) {
- DocTagsFromIElementFactory.getInstance(
- MarkdownTokenTypes.TEXT,
- params = params,
- children = linkText.children.drop(1).dropLast(1).evaluateChildren(),
- body = linkTextString.removeSurrounding("[", "]")
- )
- } else {
- DocTagsFromIElementFactory.getInstance(
- MarkdownElementTypes.INLINE_LINK,
- params = params,
- children = linkText.children.drop(1).dropLast(1).evaluateChildren(),
- dri = dri
- )
- }
- }
+ return linksHandler(linkText, link)
+ }
- private fun codeLineHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance(
- MarkdownElementTypes.CODE_BLOCK,
- body = text.substring(node.startOffset, node.endOffset)
- )
+ private fun inlineLinksHandler(node: ASTNode): DocTag {
+ val linkText = node.children.find { it.type == MarkdownElementTypes.LINK_TEXT }
+ ?: throw IllegalStateException("Wrong AST Tree. Inline link does not contain expected content")
+ val linkDestination = node.children.find { it.type == MarkdownElementTypes.LINK_DESTINATION }
+ ?: throw IllegalStateException("Wrong AST Tree. Inline link does not contain expected content")
+ val linkTitle = node.children.find { it.type == MarkdownElementTypes.LINK_TITLE }
- private fun textHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance(
- MarkdownTokenTypes.TEXT,
- body = text.substring(node.startOffset, node.endOffset).transform()
- )
+ val link = text.substring(linkDestination.startOffset, linkDestination.endOffset)
- private fun markdownFileHandler(node: ASTNode) = if (node.children.size == 1)
- visitNode(node.children.first())
- else
- defaultHandler(node)
+ return linksHandler(linkText, link, linkTitle)
+ }
- private fun strikeThroughHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance(
- node.type,
- children = node.children.evaluateChildrenWithDroppedEnclosingTokens(2)
- )
+ private fun autoLinksHandler(node: ASTNode): DocTag {
+ val link = text.substring(node.startOffset + 1, node.endOffset - 1)
- private fun tableHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance(
- GFMElementTypes.TABLE,
- children = node.children.filterTabSeparators().evaluateChildren()
- )
+ return linksHandler(node, link)
+ }
- private fun headerHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance(
- GFMElementTypes.HEADER,
- children = node.children.filterTabSeparators().evaluateChildren()
- )
+ private fun linksHandler(linkText: ASTNode, link: String, linkTitle: ASTNode? = null): DocTag {
+ val dri: DRI? = resolveDRI(link)
+ val linkTextString =
+ if (linkTitle == null) link else text.substring(linkTitle.startOffset + 1, linkTitle.endOffset - 1)
- private fun rowHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance(
- GFMElementTypes.ROW,
- children = node.children.filterTabSeparators().evaluateChildren()
- )
+ val params = if (linkTitle == null)
+ mapOf("href" to link)
+ else
+ mapOf("href" to link, "title" to linkTextString)
- private fun cellHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance(
- GFMTokenTypes.CELL,
- children = node.children.filterTabSeparators().evaluateChildren().trimSurroundingTokensIfText()
- )
- private fun String.isRemoteLink() = try {
- URL(this)
- true
- } catch (e: MalformedURLException) {
- false
+ return if (dri == null && !link.isRemoteLink()) {
+ DocTagsFromIElementFactory.getInstance(
+ MarkdownTokenTypes.TEXT,
+ params = params,
+ children = linkText.children.drop(1).dropLast(1).evaluateChildren(),
+ body = linkTextString.removeSurrounding("[", "]")
+ )
+ } else {
+ DocTagsFromIElementFactory.getInstance(
+ MarkdownElementTypes.INLINE_LINK,
+ params = params,
+ children = linkText.children.drop(1).dropLast(1).evaluateChildren(),
+ dri = dri
+ )
}
+ }
- private fun imagesHandler(node: ASTNode): DocTag =
- with(node.children.last().children) {
- val destination = find { it.type == MarkdownElementTypes.LINK_DESTINATION }
- val description = find { it.type == MarkdownElementTypes.LINK_TEXT }
-
- val src = destination?.let {
- mapOf("href" to text.substring(it.startOffset, it.endOffset))
- } ?: emptyMap()
-
- val alt = description?.let {
- mapOf("alt" to text.substring(it.startOffset + 1, it.endOffset - 1))
- } ?: emptyMap()
+ private fun codeLineHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance(
+ MarkdownElementTypes.CODE_BLOCK,
+ body = text.substring(node.startOffset, node.endOffset)
+ )
+
+ private fun textHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance(
+ MarkdownTokenTypes.TEXT,
+ body = text.substring(node.startOffset, node.endOffset).transform()
+ )
+
+ private fun markdownFileHandler(node: ASTNode) = if (node.children.size == 1)
+ visitNode(node.children.first())
+ else
+ defaultHandler(node)
+
+ private fun strikeThroughHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance(
+ node.type,
+ children = node.children.evaluateChildrenWithDroppedEnclosingTokens(2)
+ )
+
+ private fun tableHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance(
+ GFMElementTypes.TABLE,
+ children = node.children.filterTabSeparators().evaluateChildren()
+ )
+
+ private fun headerHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance(
+ GFMElementTypes.HEADER,
+ children = node.children.filterTabSeparators().evaluateChildren()
+ )
+
+ private fun rowHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance(
+ GFMElementTypes.ROW,
+ children = node.children.filterTabSeparators().evaluateChildren()
+ )
+
+ private fun cellHandler(node: ASTNode) = DocTagsFromIElementFactory.getInstance(
+ GFMTokenTypes.CELL,
+ children = node.children.filterTabSeparators().evaluateChildren().trimSurroundingTokensIfText()
+ )
+
+ private fun String.isRemoteLink() = try {
+ URL(this)
+ true
+ } catch (e: MalformedURLException) {
+ false
+ }
- return DocTagsFromIElementFactory.getInstance(
- node.type,
- params = src + alt
- )
- }
+ private fun imagesHandler(node: ASTNode): DocTag =
+ with(node.children.last().children) {
+ val destination = find { it.type == MarkdownElementTypes.LINK_DESTINATION }
+ val description = find { it.type == MarkdownElementTypes.LINK_TEXT }
- private fun codeSpansHandler(node: ASTNode): DocTag =
- DocTagsFromIElementFactory.getInstance(
- node.type,
- children = listOf(
- DocTagsFromIElementFactory.getInstance(
- MarkdownTokenTypes.TEXT,
- body = text.substring(node.startOffset + 1, node.endOffset - 1).replace('\n', ' ').trimIndent()
- )
+ val src = destination?.let {
+ mapOf("href" to text.substring(it.startOffset, it.endOffset))
+ } ?: emptyMap()
- )
- )
+ val alt = description?.let {
+ mapOf("alt" to text.substring(it.startOffset + 1, it.endOffset - 1))
+ } ?: emptyMap()
- private fun codeFencesHandler(node: ASTNode): DocTag =
- DocTagsFromIElementFactory.getInstance(
+ return DocTagsFromIElementFactory.getInstance(
node.type,
- children = node
- .children
- .dropWhile { it.type != MarkdownTokenTypes.CODE_FENCE_CONTENT }
- .dropLastWhile { it.type != MarkdownTokenTypes.CODE_FENCE_CONTENT }
- .map {
- if (it.type == MarkdownTokenTypes.EOL)
- LeafASTNode(MarkdownTokenTypes.HARD_LINE_BREAK, 0, 0)
- else
- it
- }.evaluateChildren(),
- params = node
- .children
- .find { it.type == MarkdownTokenTypes.FENCE_LANG }
- ?.let { mapOf("lang" to text.substring(it.startOffset, it.endOffset)) }
- ?: emptyMap()
+ params = src + alt
)
+ }
- private fun codeBlocksHandler(node: ASTNode): DocTag =
- DocTagsFromIElementFactory.getInstance(node.type, children = node.children.mergeLeafASTNodes().map {
+ private fun codeSpansHandler(node: ASTNode) =
+ DocTagsFromIElementFactory.getInstance(
+ node.type,
+ children = listOf(
DocTagsFromIElementFactory.getInstance(
MarkdownTokenTypes.TEXT,
- body = text.substring(it.startOffset, it.endOffset)
+ body = text.substring(node.startOffset + 1, node.endOffset - 1).replace('\n', ' ').trimIndent()
)
- })
- private fun defaultHandler(node: ASTNode): DocTag =
+ )
+ )
+
+ private fun codeFencesHandler(node: ASTNode) =
+ DocTagsFromIElementFactory.getInstance(
+ node.type,
+ children = node
+ .children
+ .dropWhile { it.type != MarkdownTokenTypes.CODE_FENCE_CONTENT }
+ .dropLastWhile { it.type != MarkdownTokenTypes.CODE_FENCE_CONTENT }
+ .map {
+ if (it.type == MarkdownTokenTypes.EOL)
+ LeafASTNode(MarkdownTokenTypes.HARD_LINE_BREAK, 0, 0)
+ else
+ it
+ }.evaluateChildren(),
+ params = node
+ .children
+ .find { it.type == MarkdownTokenTypes.FENCE_LANG }
+ ?.let { mapOf("lang" to text.substring(it.startOffset, it.endOffset)) }
+ ?: emptyMap()
+ )
+
+ private fun codeBlocksHandler(node: ASTNode) =
+ DocTagsFromIElementFactory.getInstance(node.type, children = node.children.mergeLeafASTNodes().map {
DocTagsFromIElementFactory.getInstance(
- MarkdownElementTypes.PARAGRAPH,
- children = node.children.evaluateChildren()
+ MarkdownTokenTypes.TEXT,
+ body = text.substring(it.startOffset, it.endOffset)
)
+ })
- fun visitNode(node: ASTNode): DocTag =
- when (node.type) {
- MarkdownElementTypes.ATX_1,
- MarkdownElementTypes.ATX_2,
- MarkdownElementTypes.ATX_3,
- MarkdownElementTypes.ATX_4,
- MarkdownElementTypes.ATX_5,
- MarkdownElementTypes.ATX_6 -> headersHandler(node)
- MarkdownTokenTypes.HORIZONTAL_RULE -> horizontalRulesHandler(node)
- MarkdownElementTypes.STRONG -> strongHandler(node)
- MarkdownElementTypes.EMPH -> emphasisHandler(node)
- MarkdownElementTypes.FULL_REFERENCE_LINK,
- 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)
- MarkdownElementTypes.CODE_BLOCK -> codeBlocksHandler(node)
- MarkdownElementTypes.CODE_FENCE -> codeFencesHandler(node)
- MarkdownElementTypes.CODE_SPAN -> codeSpansHandler(node)
- MarkdownElementTypes.IMAGE -> imagesHandler(node)
- MarkdownTokenTypes.HARD_LINE_BREAK -> DocTagsFromIElementFactory.getInstance(node.type)
- MarkdownTokenTypes.CODE_FENCE_CONTENT,
- MarkdownTokenTypes.CODE_LINE -> codeLineHandler(node)
- MarkdownTokenTypes.TEXT -> textHandler(node)
- MarkdownElementTypes.MARKDOWN_FILE -> markdownFileHandler(node)
- GFMElementTypes.STRIKETHROUGH -> strikeThroughHandler(node)
- GFMElementTypes.TABLE -> tableHandler(node)
- GFMElementTypes.HEADER -> headerHandler(node)
- GFMElementTypes.ROW -> rowHandler(node)
- GFMTokenTypes.CELL -> cellHandler(node)
- else -> defaultHandler(node)
- }
+ private fun defaultHandler(node: ASTNode) =
+ DocTagsFromIElementFactory.getInstance(
+ MarkdownElementTypes.PARAGRAPH,
+ children = node.children.evaluateChildren()
+ )
- private fun List<ASTNode>.filterTabSeparators() =
- this.filterNot { it.type == GFMTokenTypes.TABLE_SEPARATOR }
+ private fun visitNode(node: ASTNode): DocTag =
+ when (node.type) {
+ MarkdownElementTypes.ATX_1,
+ MarkdownElementTypes.ATX_2,
+ MarkdownElementTypes.ATX_3,
+ MarkdownElementTypes.ATX_4,
+ MarkdownElementTypes.ATX_5,
+ MarkdownElementTypes.ATX_6 -> headersHandler(node)
+ MarkdownTokenTypes.HORIZONTAL_RULE -> horizontalRulesHandler(node)
+ MarkdownElementTypes.STRONG -> strongHandler(node)
+ MarkdownElementTypes.EMPH -> emphasisHandler(node)
+ MarkdownElementTypes.FULL_REFERENCE_LINK,
+ 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)
+ MarkdownElementTypes.CODE_BLOCK -> codeBlocksHandler(node)
+ MarkdownElementTypes.CODE_FENCE -> codeFencesHandler(node)
+ MarkdownElementTypes.CODE_SPAN -> codeSpansHandler(node)
+ MarkdownElementTypes.IMAGE -> imagesHandler(node)
+ MarkdownTokenTypes.HARD_LINE_BREAK -> DocTagsFromIElementFactory.getInstance(node.type)
+ MarkdownTokenTypes.CODE_FENCE_CONTENT,
+ MarkdownTokenTypes.CODE_LINE -> codeLineHandler(node)
+ MarkdownTokenTypes.TEXT -> textHandler(node)
+ MarkdownElementTypes.MARKDOWN_FILE -> markdownFileHandler(node)
+ GFMElementTypes.STRIKETHROUGH -> strikeThroughHandler(node)
+ GFMElementTypes.TABLE -> tableHandler(node)
+ GFMElementTypes.HEADER -> headerHandler(node)
+ GFMElementTypes.ROW -> rowHandler(node)
+ GFMTokenTypes.CELL -> cellHandler(node)
+ else -> defaultHandler(node)
+ }
- private fun List<ASTNode>.evaluateChildren(): List<DocTag> =
- this.removeUselessTokens().mergeLeafASTNodes().map { visitNode(it) }
+ private fun List<ASTNode>.filterTabSeparators() =
+ this.filterNot { it.type == GFMTokenTypes.TABLE_SEPARATOR }
- private fun List<ASTNode>.removeUselessTokens(): List<ASTNode> =
- this.filterIndexed { index, node ->
- !(node.type == MarkdownElementTypes.LINK_DEFINITION || (
- node.type == MarkdownTokenTypes.EOL &&
- this.getOrNull(index - 1)?.type == MarkdownTokenTypes.HARD_LINE_BREAK
- ))
- }
+ private fun List<ASTNode>.evaluateChildren(): List<DocTag> =
+ this.removeUselessTokens().mergeLeafASTNodes().map { visitNode(it) }
- private fun List<DocTag>.trimSurroundingTokensIfText() = mapIndexed { index, elem ->
- val elemTransformed = if (index == 0 && elem is Text) elem.copy(elem.body.trimStart()) else elem
- if (index == lastIndex && elemTransformed is Text) elemTransformed.copy(elemTransformed.body.trimEnd()) else elemTransformed
+ private fun List<ASTNode>.removeUselessTokens(): List<ASTNode> =
+ this.filterIndexed { index, node ->
+ !(node.type == MarkdownElementTypes.LINK_DEFINITION || (
+ node.type == MarkdownTokenTypes.EOL &&
+ this.getOrNull(index - 1)?.type == MarkdownTokenTypes.HARD_LINE_BREAK
+ ))
}
- private val notLeafNodes = listOf(MarkdownTokenTypes.HORIZONTAL_RULE, MarkdownTokenTypes.HARD_LINE_BREAK)
-
- private fun List<ASTNode>.isNotLeaf(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.isNotLeaf(index)) {
- children += this[index]
- } else {
- val startOffset = this[index].startOffset
- 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
- }
- break
- }
- index++
- }
- if (index == this.lastIndex) {
+ private fun List<DocTag>.trimSurroundingTokensIfText() = mapIndexed { index, elem ->
+ val elemTransformed = if (index == 0 && elem is Text) elem.copy(elem.body.trimStart()) else elem
+ if (index == lastIndex && elemTransformed is Text) elemTransformed.copy(elemTransformed.body.trimEnd()) else elemTransformed
+ }
+
+ private val notLeafNodes = listOf(MarkdownTokenTypes.HORIZONTAL_RULE, MarkdownTokenTypes.HARD_LINE_BREAK)
+
+ private fun List<ASTNode>.isNotLeaf(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.isNotLeaf(index)) {
+ children += this[index]
+ } else {
+ val startOffset = this[index].startOffset
+ 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
}
+ break
+ }
+ index++
+ }
+ if (index == this.lastIndex) {
+ mergedLeafNode(this, index, startOffset, sIndex)?.run {
+ children += this
}
}
- index++
}
- return children
+ index++
}
+ return children
+ }
- 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
+ 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)
}
-
- private fun String.transform() = this
- .replace(Regex("\n\n+"), "") // Squashing new lines between paragraphs
- .replace(Regex("\n"), " ")
- .replace(Regex(" >+ +"), " ") // Replacement used in blockquotes, get rid of garbage
+ return null
}
+ private fun String.transform() = this
+ .replace(Regex("\n\n+"), "") // Squashing new lines between paragraphs
+ .replace(Regex("\n"), " ")
+ .replace(Regex(" >+ +"), " ") // Replacement used in blockquotes, get rid of garbage
- 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 = GFMFlavourDescriptor()
- val markdownAstRoot: ASTNode = IntellijMarkdownParser(flavourDescriptor).buildMarkdownTreeFromString(text)
-
- return MarkdownVisitor(text, getAllDestinationLinks(text, markdownAstRoot).toMap()).visitNode(markdownAstRoot)
- }
-
- override fun parseStringToDocNode(extractedString: String) = markdownToDocNode(extractedString)
- override fun preparse(text: String) = text
- private fun findParent(kDoc: PsiElement): PsiElement =
- if (kDoc is KDocSection) findParent(kDoc.parent) else kDoc
+ companion object {
- private fun getAllKDocTags(kDocImpl: PsiElement): List<KDocTag> =
- kDocImpl.children.filterIsInstance<KDocTag>().filterNot { it is KDocSection } + kDocImpl.children.flatMap {
- getAllKDocTags(
- it
- )
+ fun parseFromKDocTag(kDocTag: KDocTag?, externalDri: (String) -> DRI?): DocumentationNode {
+ return if (kDocTag == null) {
+ DocumentationNode(emptyList())
+ } else {
+ fun parseStringToDocNode(text: String) = MarkdownParser(externalDri).parseStringToDocNode(text)
+ DocumentationNode(
+ (listOf(kDocTag) + getAllKDocTags(findParent(kDocTag))).map {
+ when (it.knownTag) {
+ null -> if (it.name == null) Description(parseStringToDocNode(it.getContent())) else CustomTagWrapper(
+ parseStringToDocNode(it.getContent()),
+ it.name!!
+ )
+ KDocKnownTag.AUTHOR -> Author(parseStringToDocNode(it.getContent()))
+ KDocKnownTag.THROWS -> Throws(
+ parseStringToDocNode(it.getContent()),
+ it.getSubjectName().orEmpty()
+ )
+ KDocKnownTag.EXCEPTION -> Throws(
+ parseStringToDocNode(it.getContent()),
+ it.getSubjectName().orEmpty()
+ )
+ KDocKnownTag.PARAM -> Param(
+ parseStringToDocNode(it.getContent()),
+ it.getSubjectName().orEmpty()
+ )
+ KDocKnownTag.RECEIVER -> Receiver(parseStringToDocNode(it.getContent()))
+ KDocKnownTag.RETURN -> Return(parseStringToDocNode(it.getContent()))
+ KDocKnownTag.SEE -> See(
+ parseStringToDocNode(it.getContent()),
+ it.getSubjectName().orEmpty(),
+ parseStringToDocNode("[${it.getSubjectName()}]")
+ .let {
+ val link = it.children[0]
+ if (link is DocumentationLink) link.dri
+ else null
+ }
+ )
+ KDocKnownTag.SINCE -> Since(parseStringToDocNode(it.getContent()))
+ KDocKnownTag.CONSTRUCTOR -> Constructor(parseStringToDocNode(it.getContent()))
+ KDocKnownTag.PROPERTY -> Property(
+ parseStringToDocNode(it.getContent()),
+ it.getSubjectName().orEmpty()
+ )
+ KDocKnownTag.SAMPLE -> Sample(
+ parseStringToDocNode(it.getContent()),
+ it.getSubjectName().orEmpty()
+ )
+ KDocKnownTag.SUPPRESS -> Suppress(parseStringToDocNode(it.getContent()))
+ }
+ }
+ )
+ }
}
- fun parseFromKDocTag(kDocTag: KDocTag?): DocumentationNode {
- return if (kDocTag == null)
- DocumentationNode(emptyList())
- else
- DocumentationNode(
- (listOf(kDocTag) + getAllKDocTags(findParent(kDocTag))).map {
- when (it.knownTag) {
- null -> if (it.name == null) Description(parseStringToDocNode(it.getContent())) else CustomTagWrapper(
- parseStringToDocNode(it.getContent()),
- it.name!!
- )
- KDocKnownTag.AUTHOR -> Author(parseStringToDocNode(it.getContent()))
- KDocKnownTag.THROWS -> Throws(
- parseStringToDocNode(it.getContent()),
- it.getSubjectName().orEmpty()
- )
- KDocKnownTag.EXCEPTION -> Throws(
- parseStringToDocNode(it.getContent()),
- it.getSubjectName().orEmpty()
- )
- KDocKnownTag.PARAM -> Param(
- parseStringToDocNode(it.getContent()),
- it.getSubjectName().orEmpty()
- )
- KDocKnownTag.RECEIVER -> Receiver(parseStringToDocNode(it.getContent()))
- KDocKnownTag.RETURN -> Return(parseStringToDocNode(it.getContent()))
- KDocKnownTag.SEE -> See(
- parseStringToDocNode(it.getContent()),
- it.getSubjectName().orEmpty(),
- parseStringToDocNode("[${it.getSubjectName()}]")
- .let {
- val link = it.children[0]
- if (link is DocumentationLink) link.dri
- else null
- }
- )
- KDocKnownTag.SINCE -> Since(parseStringToDocNode(it.getContent()))
- KDocKnownTag.CONSTRUCTOR -> Constructor(parseStringToDocNode(it.getContent()))
- KDocKnownTag.PROPERTY -> Property(
- parseStringToDocNode(it.getContent()),
- it.getSubjectName().orEmpty()
- )
- KDocKnownTag.SAMPLE -> Sample(
- parseStringToDocNode(it.getContent()),
- it.getSubjectName().orEmpty()
- )
- KDocKnownTag.SUPPRESS -> Suppress(parseStringToDocNode(it.getContent()))
- }
- }
- )
+ private fun findParent(kDoc: PsiElement): PsiElement =
+ if (kDoc is KDocSection) findParent(kDoc.parent) else kDoc
+
+ private fun getAllKDocTags(kDocImpl: PsiElement): List<KDocTag> =
+ kDocImpl.children.filterIsInstance<KDocTag>().filterNot { it is KDocSection } + kDocImpl.children.flatMap {
+ getAllKDocTags(it)
+ }
}
}
+
diff --git a/plugins/base/src/main/kotlin/parsers/Parser.kt b/plugins/base/src/main/kotlin/parsers/Parser.kt
index 894fa82f..11b2fdcf 100644
--- a/plugins/base/src/main/kotlin/parsers/Parser.kt
+++ b/plugins/base/src/main/kotlin/parsers/Parser.kt
@@ -8,7 +8,7 @@ abstract class Parser {
abstract fun preparse(text: String): String
- fun parse(text: String): DocumentationNode {
+ open fun parse(text: String): DocumentationNode {
val list = jkdocToListOfPairs(preparse(text))
diff --git a/plugins/base/src/main/kotlin/parsers/moduleAndPackage/ModuleAndPackageDocumentationParsingContext.kt b/plugins/base/src/main/kotlin/parsers/moduleAndPackage/ModuleAndPackageDocumentationParsingContext.kt
index f11a5d13..64441447 100644
--- a/plugins/base/src/main/kotlin/parsers/moduleAndPackage/ModuleAndPackageDocumentationParsingContext.kt
+++ b/plugins/base/src/main/kotlin/parsers/moduleAndPackage/ModuleAndPackageDocumentationParsingContext.kt
@@ -3,10 +3,17 @@
package org.jetbrains.dokka.base.parsers.moduleAndPackage
import org.jetbrains.dokka.analysis.DokkaResolutionFacade
+import org.jetbrains.dokka.analysis.from
import org.jetbrains.dokka.base.parsers.MarkdownParser
import org.jetbrains.dokka.base.parsers.moduleAndPackage.ModuleAndPackageDocumentation.Classifier.*
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.doc.Description
import org.jetbrains.dokka.model.doc.DocumentationNode
import org.jetbrains.dokka.utilities.DokkaLogger
+import org.jetbrains.kotlin.descriptors.ClassDescriptor
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.descriptors.FunctionDescriptor
+import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
@@ -22,15 +29,38 @@ internal fun ModuleAndPackageDocumentationParsingContext.parse(
internal fun ModuleAndPackageDocumentationParsingContext(
logger: DokkaLogger,
- facade: DokkaResolutionFacade? = null,
+ facade: DokkaResolutionFacade? = null
) = ModuleAndPackageDocumentationParsingContext { fragment ->
val descriptor = when (fragment.classifier) {
Module -> facade?.moduleDescriptor?.getPackage(FqName.topLevel(Name.identifier("")))
Package -> facade?.moduleDescriptor?.getPackage(FqName(fragment.name))
}
- MarkdownParser(
- resolutionFacade = facade,
- declarationDescriptor = descriptor,
- logger = logger
- )
+
+ val externalDri = { link: String ->
+ try {
+ if (facade != null && descriptor != null) {
+ resolveKDocLink(
+ facade.resolveSession.bindingContext,
+ facade,
+ descriptor,
+ null,
+ link.split('.')
+ ).sorted().firstOrNull()?.let { DRI.from(it) }
+ } else null
+ } catch (e1: IllegalArgumentException) {
+ logger.warn("Couldn't resolve link for $link")
+ null
+ }
+ }
+
+ MarkdownParser(externalDri = externalDri)
}
+
+private fun Collection<DeclarationDescriptor>.sorted() = sortedWith(
+ compareBy(
+ { it is ClassDescriptor },
+ { (it as? FunctionDescriptor)?.name },
+ { (it as? FunctionDescriptor)?.valueParameters?.size },
+ { (it as? FunctionDescriptor)?.valueParameters?.joinToString { it.type.toString() } }
+ )
+)
diff --git a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt
index 68b69719..ecfd5172 100644
--- a/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt
+++ b/plugins/base/src/main/kotlin/translators/descriptors/DefaultDescriptorToDocumentableTranslator.kt
@@ -12,7 +12,6 @@ import org.jetbrains.dokka.model.*
import org.jetbrains.dokka.model.Nullable
import org.jetbrains.dokka.model.TypeConstructor
import org.jetbrains.dokka.model.doc.*
-import org.jetbrains.dokka.model.properties.ExtraProperty
import org.jetbrains.dokka.model.properties.PropertyContainer
import org.jetbrains.dokka.plugability.DokkaContext
import org.jetbrains.dokka.transformers.sources.SourceToDocumentableTranslator
@@ -30,6 +29,7 @@ import org.jetbrains.kotlin.descriptors.annotations.Annotated
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.descriptors.impl.DeclarationDescriptorVisitorEmptyBodies
import org.jetbrains.kotlin.idea.kdoc.findKDoc
+import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink
import org.jetbrains.kotlin.load.kotlin.toSourceElement
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
@@ -683,7 +683,23 @@ private class DokkaDescriptorVisitor(
}
private fun DeclarationDescriptor.getDocumentation() = findKDoc().let {
- MarkdownParser(resolutionFacade, this, logger).parseFromKDocTag(it)
+ MarkdownParser.parseFromKDocTag(
+ kDocTag = it,
+ externalDri = { link: String ->
+ try {
+ resolveKDocLink(
+ context = resolutionFacade.resolveSession.bindingContext,
+ resolutionFacade = resolutionFacade,
+ fromDescriptor = this,
+ fromSubjectOfTag = null,
+ qualifiedName = link.split('.')
+ ).firstOrNull()?.let { DRI.from(it) }
+ } catch (e1: IllegalArgumentException) {
+ logger.warn("Couldn't resolve link for $link")
+ null
+ }
+ }
+ )
}.takeIf { it.children.isNotEmpty() }
private fun ClassDescriptor.companion(dri: DRIWithPlatformInfo): DObject? = companionObjectDescriptor?.let {