1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
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<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
}
}
|