Skip to content

Commit ae2e880

Browse files
committed
Add documentation with the type signature and kdoc
1 parent fe0b7b0 commit ae2e880

File tree

4 files changed

+181
-11
lines changed

4 files changed

+181
-11
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package snapshots
2+
// ^^^^^^^^^ reference snapshots/
3+
4+
import java.io.Serializable
5+
// ^^^^ reference java/
6+
// ^^ reference java/io/
7+
// ^^^^^^^^^^^^ reference java/io/Serializable#
8+
9+
abstract class DocstringSuperclass
10+
// ^^^^^^^^^^^^^^^^^^^ definition snapshots/DocstringSuperclass# DocstringSuperclass
11+
// ^^^^^^^^^^^^^^^^^^^ definition snapshots/DocstringSuperclass#`<init>`(). DocstringSuperclass
12+
/** Example class docstring. */
13+
class Docstrings : DocstringSuperclass(), Serializable {
14+
// ^^^^^^^^^^ definition snapshots/Docstrings# Docstrings
15+
// ^^^^^^^^^^ definition snapshots/Docstrings#`<init>`(). Docstrings
16+
// ^^^^^^^^^^^^^^^^^^^ reference snapshots/DocstringSuperclass#`<init>`().
17+
// ^^^^^^^^^^^^ reference java/io/Serializable#
18+
}
19+
20+
/** Example method docstring. */
21+
fun docstrings() { }
22+
// ^^^^^^^^^^ definition snapshots/DocstringsKt#docstrings(). docstrings
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package snapshots
2+
3+
import java.io.Serializable
4+
5+
abstract class DocstringSuperclass
6+
/** Example class docstring. */
7+
class Docstrings : DocstringSuperclass(), Serializable {
8+
}
9+
10+
/** Example method docstring. */
11+
fun docstrings() { }

semanticdb-kotlinc/src/main/kotlin/com/sourcegraph/semanticdb_kotlinc/SemanticdbTextDocumentBuilder.kt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,24 @@ package com.sourcegraph.semanticdb_kotlinc
22

33
import com.sourcegraph.semanticdb_kotlinc.Semanticdb.SymbolOccurrence.Role
44
import java.lang.IllegalArgumentException
5+
import java.lang.StringBuilder
56
import java.nio.file.Path
67
import java.nio.file.Paths
78
import java.security.MessageDigest
89
import kotlin.contracts.ExperimentalContracts
910
import kotlin.text.Charsets.UTF_8
1011
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
12+
import org.jetbrains.kotlin.backend.common.serialization.metadata.findKDocString
1113
import org.jetbrains.kotlin.com.intellij.lang.java.JavaLanguage
1214
import org.jetbrains.kotlin.com.intellij.navigation.NavigationItem
1315
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
1416
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
17+
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
1518
import org.jetbrains.kotlin.idea.KotlinLanguage
1619
import org.jetbrains.kotlin.psi.KtConstructor
1720
import org.jetbrains.kotlin.psi.KtFile
1821
import org.jetbrains.kotlin.psi.KtPropertyAccessor
22+
import org.jetbrains.kotlin.renderer.DescriptorRenderer
1923

2024
@ExperimentalContracts
2125
class SemanticdbTextDocumentBuilder(
@@ -55,6 +59,7 @@ class SemanticdbTextDocumentBuilder(
5559
return SymbolInformation {
5660
this.symbol = symbol.toString()
5761
this.displayName = displayName(element)
62+
this.documentation = semanticdbDocumentation(descriptor)
5863
this.language =
5964
when (element.language) {
6065
is KotlinLanguage -> Semanticdb.Language.KOTLIN
@@ -102,6 +107,48 @@ class SemanticdbTextDocumentBuilder(
102107
"%02X".format(it)
103108
}
104109

110+
private fun semanticdbDocumentation(
111+
descriptor: DeclarationDescriptor
112+
): Semanticdb.Documentation = Documentation {
113+
format = Semanticdb.Documentation.Format.MARKDOWN
114+
val signature = DescriptorRenderer.COMPACT.render(descriptor)
115+
val kdoc =
116+
when (descriptor) {
117+
is DeclarationDescriptorWithSource -> descriptor.findKDocString() ?: ""
118+
else -> ""
119+
}
120+
message = "```kt\n$signature\n```${stripKDocAsterisks(kdoc)}"
121+
}
122+
123+
// Returns the kdoc string with all leading and trailing "/*" tokens removed. Naive
124+
// implementation that can
125+
// be replaced with a utility method from the compiler in the future, if one exists.
126+
private fun stripKDocAsterisks(kdoc: String): String {
127+
if (kdoc.isEmpty()) return kdoc
128+
val out = StringBuilder().append("\n\n").append("----").append("\n")
129+
kdoc.lineSequence().forEach { line ->
130+
var start = 0
131+
while (start < line.length && Character.isWhitespace(line[start])) {
132+
start++
133+
}
134+
if (start < line.length && line[start] == '/') {
135+
start++
136+
}
137+
while (start < line.length && line[start] == '*') {
138+
start++
139+
}
140+
while (start < line.length && Character.isWhitespace(line[start])) {
141+
start++
142+
}
143+
var end = if (line.endsWith("*/")) line.length - 3 else line.length - 1
144+
while (end > start && Character.isWhitespace(line[end])) {
145+
end--
146+
}
147+
out.append("\n").append(line, start, end)
148+
}
149+
return out.toString()
150+
}
151+
105152
companion object {
106153
private fun displayName(element: PsiElement): String =
107154
when (element) {

semanticdb-kotlinc/src/test/kotlin/com/sourcegraph/semanticdb_kotlinc/test/AnalyzerTest.kt

Lines changed: 101 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,21 @@ import io.kotest.assertions.assertSoftly
1111
import io.kotest.assertions.withClue
1212
import io.kotest.matchers.collections.shouldContain
1313
import io.kotest.matchers.shouldBe
14+
import io.kotest.matchers.shouldNotBe
1415
import java.io.File
1516
import java.nio.file.Path
1617
import kotlin.contracts.ExperimentalContracts
1718
import kotlin.test.Test
19+
import kotlin.test.assertEquals
20+
import org.intellij.lang.annotations.Language
1821
import org.junit.jupiter.api.io.TempDir
1922

2023
@ExperimentalContracts
2124
class AnalyzerTest {
22-
@Test
23-
fun `basic test`(@TempDir path: Path) {
24-
val buildPath = File(path.resolve("build").toString()).apply { mkdir() }
25-
26-
val source =
27-
SourceFile.testKt(
28-
"""
29-
package sample
30-
class Banana {
31-
fun foo() { }
32-
}""")
3325

26+
fun compileSemanticdb(path: Path, @Language("kotlin") code: String): TextDocument {
27+
val buildPath = File(path.resolve("build").toString()).apply { mkdir() }
28+
val source = SourceFile.testKt(code)
3429
lateinit var document: TextDocument
3530

3631
val result =
@@ -49,6 +44,21 @@ class AnalyzerTest {
4944
.compile()
5045

5146
result.exitCode shouldBe KotlinCompilation.ExitCode.OK
47+
document shouldNotBe null
48+
return document
49+
}
50+
51+
@Test
52+
fun `basic test`(@TempDir path: Path) {
53+
val document =
54+
compileSemanticdb(
55+
path,
56+
"""
57+
package sample
58+
class Banana {
59+
fun foo() { }
60+
}""")
61+
5262
val occurrences =
5363
arrayOf(
5464
SymbolOccurrence {
@@ -91,11 +101,21 @@ class AnalyzerTest {
91101
symbol = "sample/Banana#"
92102
language = KOTLIN
93103
displayName = "Banana"
104+
documentation =
105+
Documentation {
106+
format = Semanticdb.Documentation.Format.MARKDOWN
107+
message = "```kt\nclass Banana\n```"
108+
}
94109
},
95110
SymbolInformation {
96111
symbol = "sample/Banana#foo()."
97112
language = KOTLIN
98113
displayName = "foo"
114+
documentation =
115+
Documentation {
116+
format = Semanticdb.Documentation.Format.MARKDOWN
117+
message = "```kt\nfun foo(): kotlin.Unit\n```"
118+
}
99119
})
100120
assertSoftly(document.symbolsList) { withClue(this) { symbols.forEach(::shouldContain) } }
101121
}
@@ -536,4 +556,74 @@ class AnalyzerTest {
536556

537557
result.exitCode shouldBe KotlinCompilation.ExitCode.OK
538558
}
559+
560+
@Test
561+
fun documentation(@TempDir path: Path) {
562+
val document =
563+
compileSemanticdb(
564+
path,
565+
"""
566+
package sample
567+
import java.io.Serializable
568+
abstract class DocstringSuperclass
569+
570+
/** Example class docstring. */
571+
class Docstrings: DocstringSuperclass(), Serializable
572+
573+
/** Example method docstring. */
574+
inline fun docstrings(msg: String): Int { return msg.length }
575+
""".trimIndent())
576+
val obtainedSymbols =
577+
document
578+
.symbolsList
579+
.map { "----------\n${it.symbol}\n${it.documentation.message}" }
580+
.joinToString("\n")
581+
val expectedSymbols =
582+
"""
583+
----------
584+
sample/DocstringSuperclass#
585+
```kt
586+
class DocstringSuperclass
587+
```
588+
----------
589+
sample/DocstringSuperclass#`<init>`().
590+
```kt
591+
constructor DocstringSuperclass()
592+
```
593+
----------
594+
sample/Docstrings#
595+
```kt
596+
class Docstrings : sample.DocstringSuperclass, java.io.Serializable
597+
```
598+
599+
----
600+
601+
Example class docstring
602+
----------
603+
sample/Docstrings#`<init>`().
604+
```kt
605+
constructor Docstrings()
606+
```
607+
608+
----
609+
610+
Example class docstring
611+
----------
612+
sample/TestKt#docstrings().
613+
```kt
614+
inline fun docstrings(msg: kotlin.String): kotlin.Int
615+
```
616+
617+
----
618+
619+
Example method docstring
620+
----------
621+
sample/TestKt#docstrings().(msg)
622+
```kt
623+
value-parameter msg: kotlin.String
624+
```
625+
""".trim()
626+
println(obtainedSymbols)
627+
assertEquals(expectedSymbols, obtainedSymbols)
628+
}
539629
}

0 commit comments

Comments
 (0)