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 { abstract fun parseStringToDocNode(extractedString: String): DocTag abstract fun preparse(text: String): String open fun parse(text: String): DocumentationNode = DocumentationNode(extractTagsToListOfPairs(preparse(text)).map { (tag, content) -> parseTagWithBody(tag, content) }) open fun parseTagWithBody(tagName: String, content: String): TagWrapper = when (tagName) { "description" -> Description(parseStringToDocNode(content)) "author" -> Author(parseStringToDocNode(content)) "version" -> Version(parseStringToDocNode(content)) "since" -> Since(parseStringToDocNode(content)) "see" -> See( parseStringToDocNode(content.substringAfter(' ')), content.substringBefore(' '), null ) "param" -> Param( parseStringToDocNode(content.substringAfter(' ')), content.substringBefore(' ') ) "property" -> Property( parseStringToDocNode(content.substringAfter(' ')), content.substringBefore(' ') ) "return" -> Return(parseStringToDocNode(content)) "constructor" -> Constructor(parseStringToDocNode(content)) "receiver" -> Receiver(parseStringToDocNode(content)) "throws", "exception" -> Throws( parseStringToDocNode(content.substringAfter(' ')), content.substringBefore(' '), null ) "deprecated" -> Deprecated(parseStringToDocNode(content)) "sample" -> Sample( parseStringToDocNode(content.substringAfter(' ')), content.substringBefore(' ') ) "suppress" -> Suppress(parseStringToDocNode(content)) else -> CustomTagWrapper(parseStringToDocNode(content), tagName) } /** * 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> = "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 { var countOfBackticks = 0 var countOfTildes = 0 var countOfBackticksInOpeningFence = 0 var countOfTildesInOpeningFence = 0 var isInCode = false val result = mutableListOf() 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 } }