plugin docstring parsing now respect @param, @retunm, @throws and general support for such tags introduced
This commit is contained in:
parent
1efa96a990
commit
9924d02cb4
@ -518,18 +518,26 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
if (d.mutable) "var ${d.name}${typeStr}" else "val ${d.name}${typeStr}"
|
if (d.mutable) "var ${d.name}${typeStr}" else "val ${d.name}${typeStr}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Show full detailed documentation, not just the summary
|
|
||||||
val raw = d.doc?.raw
|
|
||||||
val doc: String? = if (raw.isNullOrBlank()) null else MarkdownRenderer.render(raw)
|
|
||||||
val sb = StringBuilder()
|
val sb = StringBuilder()
|
||||||
sb.append("<div class='doc-title'>").append(htmlEscape(title)).append("</div>")
|
sb.append("<div class='doc-title'>").append(htmlEscape(title)).append("</div>")
|
||||||
if (!doc.isNullOrBlank()) sb.append(styledMarkdown(doc))
|
sb.append(renderDocBody(d.doc))
|
||||||
return sb.toString()
|
return sb.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderParamDoc(fn: MiniFunDecl, p: MiniParam): String {
|
private fun renderParamDoc(fn: MiniFunDecl, p: MiniParam): String {
|
||||||
val title = "parameter ${p.name}${typeOf(p.type)} in ${fn.name}${signatureOf(fn)}"
|
val title = "parameter ${p.name}${typeOf(p.type)} in ${fn.name}${signatureOf(fn)}"
|
||||||
return "<div class='doc-title'>${htmlEscape(title)}</div>"
|
val sb = StringBuilder()
|
||||||
|
sb.append("<div class='doc-title'>").append(htmlEscape(title)).append("</div>")
|
||||||
|
|
||||||
|
// Find matching @param tag
|
||||||
|
fn.doc?.tags?.get("param")?.forEach { tag ->
|
||||||
|
val parts = tag.split(Regex("\\s+"), 2)
|
||||||
|
if (parts.getOrNull(0) == p.name && parts.size > 1) {
|
||||||
|
sb.append(styledMarkdown(MarkdownRenderer.render(parts[1])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderMemberFunDoc(className: String, m: MiniMemberFunDecl): String {
|
private fun renderMemberFunDoc(className: String, m: MiniMemberFunDecl): String {
|
||||||
@ -540,11 +548,9 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
val ret = typeOf(m.returnType)
|
val ret = typeOf(m.returnType)
|
||||||
val staticStr = if (m.isStatic) "static " else ""
|
val staticStr = if (m.isStatic) "static " else ""
|
||||||
val title = "${staticStr}method $className.${m.name}(${params})${ret}"
|
val title = "${staticStr}method $className.${m.name}(${params})${ret}"
|
||||||
val raw = m.doc?.raw
|
|
||||||
val doc: String? = if (raw.isNullOrBlank()) null else MarkdownRenderer.render(raw)
|
|
||||||
val sb = StringBuilder()
|
val sb = StringBuilder()
|
||||||
sb.append("<div class='doc-title'>").append(htmlEscape(title)).append("</div>")
|
sb.append("<div class='doc-title'>").append(htmlEscape(title)).append("</div>")
|
||||||
if (!doc.isNullOrBlank()) sb.append(styledMarkdown(doc))
|
sb.append(renderDocBody(m.doc))
|
||||||
return sb.toString()
|
return sb.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -553,11 +559,9 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
val kind = if (m.mutable) "var" else "val"
|
val kind = if (m.mutable) "var" else "val"
|
||||||
val staticStr = if (m.isStatic) "static " else ""
|
val staticStr = if (m.isStatic) "static " else ""
|
||||||
val title = "${staticStr}${kind} $className.${m.name}${ts}"
|
val title = "${staticStr}${kind} $className.${m.name}${ts}"
|
||||||
val raw = m.doc?.raw
|
|
||||||
val doc: String? = if (raw.isNullOrBlank()) null else MarkdownRenderer.render(raw)
|
|
||||||
val sb = StringBuilder()
|
val sb = StringBuilder()
|
||||||
sb.append("<div class='doc-title'>").append(htmlEscape(title)).append("</div>")
|
sb.append("<div class='doc-title'>").append(htmlEscape(title)).append("</div>")
|
||||||
if (!doc.isNullOrBlank()) sb.append(styledMarkdown(doc))
|
sb.append(renderDocBody(m.doc))
|
||||||
return sb.toString()
|
return sb.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -663,6 +667,55 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
return if (e > s) TextRange(s, e) else null
|
return if (e > s) TextRange(s, e) else null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun renderDocBody(doc: MiniDoc?): String {
|
||||||
|
if (doc == null) return ""
|
||||||
|
val sb = StringBuilder()
|
||||||
|
if (doc.raw.isNotBlank()) {
|
||||||
|
sb.append(styledMarkdown(MarkdownRenderer.render(doc.raw)))
|
||||||
|
}
|
||||||
|
if (doc.tags.isNotEmpty()) {
|
||||||
|
sb.append(renderTags(doc.tags))
|
||||||
|
}
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderTags(tags: Map<String, List<String>>): String {
|
||||||
|
if (tags.isEmpty()) return ""
|
||||||
|
val sb = StringBuilder()
|
||||||
|
sb.append("<table class='sections'>")
|
||||||
|
|
||||||
|
fun section(title: String, list: List<String>, isKeyValue: Boolean = false) {
|
||||||
|
if (list.isEmpty()) return
|
||||||
|
sb.append("<tr><td valign='top' class='section'><p>").append(htmlEscape(title)).append(":</p></td><td valign='top'>")
|
||||||
|
list.forEachIndexed { index, item ->
|
||||||
|
if (index > 0) sb.append("<p>")
|
||||||
|
if (isKeyValue) {
|
||||||
|
val parts = item.split(Regex("\\s+"), 2)
|
||||||
|
sb.append("<code>").append(htmlEscape(parts[0])).append("</code>")
|
||||||
|
if (parts.size > 1) {
|
||||||
|
sb.append(" — ").append(MarkdownRenderer.render(parts[1]).removePrefix("<p>").removeSuffix("</p>"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sb.append(MarkdownRenderer.render(item).removePrefix("<p>").removeSuffix("</p>"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.append("</td></tr>")
|
||||||
|
}
|
||||||
|
|
||||||
|
section("Parameters", tags["param"] ?: emptyList(), isKeyValue = true)
|
||||||
|
section("Returns", tags["return"] ?: emptyList())
|
||||||
|
section("Throws", tags["throws"] ?: emptyList(), isKeyValue = true)
|
||||||
|
|
||||||
|
tags.forEach { (name, list) ->
|
||||||
|
if (name !in listOf("param", "return", "throws")) {
|
||||||
|
section(name.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }, list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("</table>")
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
|
||||||
private fun previousWordBefore(text: String, offset: Int): TextRange? {
|
private fun previousWordBefore(text: String, offset: Int): TextRange? {
|
||||||
// skip spaces and the dot to the left, but stop after hitting a non-identifier boundary
|
// skip spaces and the dot to the left, but stop after hitting a non-identifier boundary
|
||||||
var i = (offset - 1).coerceAtLeast(0)
|
var i = (offset - 1).coerceAtLeast(0)
|
||||||
|
|||||||
@ -116,10 +116,8 @@ class Compiler(
|
|||||||
|
|
||||||
private fun consumePendingDoc(): MiniDoc? {
|
private fun consumePendingDoc(): MiniDoc? {
|
||||||
if (pendingDocLines.isEmpty()) return null
|
if (pendingDocLines.isEmpty()) return null
|
||||||
val raw = pendingDocLines.joinToString("\n").trimEnd()
|
|
||||||
val summary = raw.lines().firstOrNull { it.isNotBlank() }?.trim()
|
|
||||||
val start = pendingDocStart ?: cc.currentPos()
|
val start = pendingDocStart ?: cc.currentPos()
|
||||||
val doc = MiniDoc(MiniRange(start, start), raw = raw, summary = summary)
|
val doc = MiniDoc.parse(MiniRange(start, start), pendingDocLines)
|
||||||
clearPendingDoc()
|
clearPendingDoc()
|
||||||
return doc
|
return doc
|
||||||
}
|
}
|
||||||
|
|||||||
@ -255,8 +255,7 @@ class ClassDocsBuilder internal constructor(private val className: String) {
|
|||||||
private fun builtinRange() = MiniRange(Pos.builtIn, Pos.builtIn)
|
private fun builtinRange() = MiniRange(Pos.builtIn, Pos.builtIn)
|
||||||
|
|
||||||
private fun miniDoc(text: String, tags: Map<String, List<String>>): MiniDoc {
|
private fun miniDoc(text: String, tags: Map<String, List<String>>): MiniDoc {
|
||||||
val summary = text.lineSequence().map { it.trim() }.firstOrNull { it.isNotEmpty() }
|
return MiniDoc.parse(builtinRange(), listOf(text), tags)
|
||||||
return MiniDoc(range = builtinRange(), raw = text, summary = summary, tags = tags)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun TypeDoc.toDisplayName(): String = when (this) {
|
private fun TypeDoc.toDisplayName(): String = when (this) {
|
||||||
|
|||||||
@ -39,7 +39,56 @@ data class MiniDoc(
|
|||||||
val raw: String,
|
val raw: String,
|
||||||
val summary: String?,
|
val summary: String?,
|
||||||
val tags: Map<String, List<String>> = emptyMap()
|
val tags: Map<String, List<String>> = emptyMap()
|
||||||
)
|
) {
|
||||||
|
companion object {
|
||||||
|
fun parse(range: MiniRange, lines: Iterable<String>, extraTags: Map<String, List<String>> = emptyMap()): MiniDoc {
|
||||||
|
val parsedTags = mutableMapOf<String, MutableList<String>>()
|
||||||
|
var currentTag: String? = null
|
||||||
|
val currentContent = StringBuilder()
|
||||||
|
|
||||||
|
fun flush() {
|
||||||
|
currentTag?.let { tag ->
|
||||||
|
parsedTags.getOrPut(tag) { mutableListOf() }.add(currentContent.toString().trim())
|
||||||
|
}
|
||||||
|
currentContent.setLength(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
val descriptionLines = mutableListOf<String>()
|
||||||
|
var inTags = false
|
||||||
|
|
||||||
|
for (rawLine in lines) {
|
||||||
|
for (line in rawLine.lines()) {
|
||||||
|
val trimmed = line.trim()
|
||||||
|
if (trimmed.startsWith("@")) {
|
||||||
|
inTags = true
|
||||||
|
flush()
|
||||||
|
val parts = trimmed.substring(1).split(Regex("\\s+"), 2)
|
||||||
|
currentTag = parts[0]
|
||||||
|
currentContent.append(parts.getOrNull(1) ?: "")
|
||||||
|
} else {
|
||||||
|
if (inTags) {
|
||||||
|
if (currentContent.isNotEmpty()) currentContent.append("\n")
|
||||||
|
currentContent.append(line)
|
||||||
|
} else {
|
||||||
|
descriptionLines.add(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flush()
|
||||||
|
|
||||||
|
val raw = descriptionLines.joinToString("\n").trimEnd()
|
||||||
|
val summary = raw.lines().firstOrNull { it.isNotBlank() }?.trim()
|
||||||
|
|
||||||
|
val finalTags = parsedTags.toMutableMap()
|
||||||
|
extraTags.forEach { (k, v) ->
|
||||||
|
finalTags.getOrPut(k) { mutableListOf() }.addAll(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return MiniDoc(range, raw, summary, finalTags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sealed interface MiniNode { val range: MiniRange }
|
sealed interface MiniNode { val range: MiniRange }
|
||||||
|
|
||||||
|
|||||||
@ -473,4 +473,58 @@ class MiniAstTest {
|
|||||||
assertEquals("a", fn.params[0].name)
|
assertEquals("a", fn.params[0].name)
|
||||||
assertEquals("b", fn.params[1].name)
|
assertEquals("b", fn.params[1].name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun miniAst_captures_dokka_tags() = runTest {
|
||||||
|
val code = """
|
||||||
|
/**
|
||||||
|
* Testing tags.
|
||||||
|
* @param x the x value
|
||||||
|
* @param y the y value
|
||||||
|
* @return some string
|
||||||
|
* @throws Exception if failed
|
||||||
|
*/
|
||||||
|
fun tagged(x: Int, y: Int): String { "" }
|
||||||
|
""".trimIndent()
|
||||||
|
val (_, sink) = compileWithMini(code)
|
||||||
|
val mini = sink.build()
|
||||||
|
assertNotNull(mini)
|
||||||
|
val fn = mini.declarations.filterIsInstance<MiniFunDecl>().firstOrNull { it.name == "tagged" }
|
||||||
|
assertNotNull(fn)
|
||||||
|
val doc = fn.doc
|
||||||
|
assertNotNull(doc)
|
||||||
|
assertEquals("Testing tags.", doc.summary)
|
||||||
|
|
||||||
|
val tags = doc.tags
|
||||||
|
assertTrue(tags.containsKey("param"), "should have @param tags")
|
||||||
|
assertEquals(listOf("x the x value", "y the y value"), tags["param"])
|
||||||
|
assertEquals(listOf("some string"), tags["return"])
|
||||||
|
assertEquals(listOf("Exception if failed"), tags["throws"])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun miniAst_captures_multiline_tags() = runTest {
|
||||||
|
val code = """
|
||||||
|
/**
|
||||||
|
* Multi line tag.
|
||||||
|
* @param x first line of x
|
||||||
|
* second line of x
|
||||||
|
* @return return value
|
||||||
|
*/
|
||||||
|
fun multiline(x: Int): Int { 0 }
|
||||||
|
""".trimIndent()
|
||||||
|
val (_, sink) = compileWithMini(code)
|
||||||
|
val mini = sink.build()
|
||||||
|
assertNotNull(mini)
|
||||||
|
val fn = mini.declarations.filterIsInstance<MiniFunDecl>().firstOrNull { it.name == "multiline" }
|
||||||
|
assertNotNull(fn)
|
||||||
|
val doc = fn.doc
|
||||||
|
assertNotNull(doc)
|
||||||
|
|
||||||
|
val tags = doc.tags
|
||||||
|
assertTrue(tags.containsKey("param"), "should have @param tags")
|
||||||
|
val xParam = tags["param"]?.first() ?: ""
|
||||||
|
assertTrue(xParam.contains("first line of x"), "should contain first line")
|
||||||
|
assertTrue(xParam.contains("second line of x"), "should contain second line")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user