Skip to content

Commit 8f85256

Browse files
committed
feat(rust): add annotation support
1 parent 1bd5ee0 commit 8f85256

File tree

3 files changed

+80
-9
lines changed

3 files changed

+80
-9
lines changed

chapi-ast-rust/src/main/kotlin/chapi/ast/rustast/RustAstBaseListener.kt

+47-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package chapi.ast.rustast
22

33
import chapi.ast.antlr.RustParser
4+
import chapi.ast.antlr.RustParser.ItemContext
5+
import chapi.ast.antlr.RustParser.MacroIdentifierLikeTokenContext
46
import chapi.ast.antlr.RustParser.TypePathSegmentContext
57
import chapi.ast.antlr.RustParser.Type_Context
68
import chapi.ast.antlr.RustParserBaseListener
@@ -72,26 +74,53 @@ open class RustAstBaseListener(private val fileName: String) : RustParserBaseLis
7274
Imports = imports
7375
}
7476

75-
fun buildPosition(ctx: ParserRuleContext): CodePosition {
76-
val position = CodePosition()
77-
position.StartLine = ctx.start.line
78-
position.StartLinePosition = ctx.start.charPositionInLine
79-
position.StopLine = ctx.stop.line
80-
position.StopLinePosition = ctx.stop.charPositionInLine
81-
return position
77+
override fun enterOuterAttribute(ctx: RustParser.OuterAttributeContext?) {
78+
8279
}
8380

8481
override fun enterStructStruct(ctx: RustParser.StructStructContext?) {
85-
val structName = ctx!!.identifier().text
82+
val structName = ctx?.identifier()?.text ?: return
83+
84+
val item = ctx.parent?.parent?.parent
85+
var annotation: MutableList<CodeAnnotation> = mutableListOf()
86+
if (item is ItemContext) {
87+
annotation = buildAttribute(item.outerAttribute())
88+
}
8689

8790
val codeStruct = CodeDataStruct(
8891
NodeName = structName,
89-
Package = codeContainer.PackageName
92+
Package = codeContainer.PackageName,
93+
Annotations = annotation
9094
)
9195

9296
structMap[structName] = codeStruct
9397
}
9498

99+
private fun buildAttribute(outerAttribute: List<RustParser.OuterAttributeContext>): MutableList<CodeAnnotation> {
100+
return outerAttribute.map { attributeContext ->
101+
val annotationName = attributeContext.attr()?.simplePath()?.simplePathSegment()?.filter { it.identifier() != null }?.map {
102+
it.identifier().text
103+
}?.joinToString(".") ?: ""
104+
105+
val keyValues = attributeContext.attr().attrInput().delimTokenTree().tokenTree()
106+
.mapNotNull {
107+
it.tokenTreeToken()
108+
.mapNotNull { token -> token.macroIdentifierLikeToken() }
109+
.map { tokenContext ->
110+
AnnotationKeyValue(
111+
Key = tokenContext.identifier().text,
112+
Value = tokenContext.identifier().text
113+
)
114+
}
115+
}.flatten()
116+
117+
CodeAnnotation(
118+
Name = annotationName,
119+
KeyValues = keyValues
120+
)
121+
}.toMutableList()
122+
}
123+
95124
override fun exitStructStruct(ctx: RustParser.StructStructContext?) {
96125

97126
}
@@ -189,6 +218,15 @@ open class RustAstBaseListener(private val fileName: String) : RustParserBaseLis
189218
structMap[currentNode.NodeName] = currentNode
190219
}
191220

221+
private fun buildPosition(ctx: ParserRuleContext): CodePosition {
222+
val position = CodePosition()
223+
position.StartLine = ctx.start.line
224+
position.StartLinePosition = ctx.start.charPositionInLine
225+
position.StopLine = ctx.stop.line
226+
position.StopLinePosition = ctx.stop.charPositionInLine
227+
return position
228+
}
229+
192230
private fun buildDedicatedStructs(): List<CodeDataStruct> {
193231
if (individualFunctions.isEmpty() && individualFields.isEmpty()) return emptyList()
194232

chapi-ast-rust/src/test/kotlin/chapi/ast/rustast/RustFullIdentListenerTest.kt

+22
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,26 @@ class RustFullIdentListenerTest {
139139
val functions = codeContainer.DataStructures[0].Functions
140140
assertEquals(2, functions.size)
141141
}
142+
143+
@Test
144+
fun should_handle_for_attribute_as_annotation() {
145+
val str = """
146+
#[derive(Debug, Clone)]
147+
pub struct EmbeddingMatch<Embedded: Clone + Ord> {
148+
score: f32,
149+
embedding_id: String,
150+
embedding: Embedding,
151+
embedded: Embedded,
152+
}
153+
""".trimIndent()
154+
155+
val codeContainer = RustAnalyser().analysis(str, "test.rs")
156+
assertEquals(1, codeContainer.DataStructures[0].Annotations.size)
157+
158+
val codeAnnotation = codeContainer.DataStructures[0].Annotations[0]
159+
assertEquals("derive", codeAnnotation.Name)
160+
assertEquals(2, codeAnnotation.KeyValues.size)
161+
assertEquals("Debug", codeAnnotation.KeyValues[0].Value)
162+
assertEquals("Clone", codeAnnotation.KeyValues[1].Value)
163+
}
142164
}

chapi-domain/src/main/kotlin/chapi/domain/core/CodeAnnotation.kt

+11
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,17 @@ import kotlinx.serialization.Serializable
55
@Serializable
66
data class AnnotationKeyValue(var Key: String = "", var Value: String = "")
77

8+
/**
9+
* Represents a code annotation.
10+
*
11+
* In TypeScript, it is a decorator.
12+
* In Rust, it is an attribute, like `#[derive(Debug, Clone)]`
13+
* In Java/Kotlin/Scala, it is an annotation.
14+
*
15+
* @property Name The name of the annotation.
16+
* @property KeyValues The list of key-value pairs associated with the annotation.
17+
* @property Position The position of the annotation in the code.
18+
*/
819
@Serializable
920
data class CodeAnnotation(
1021
var Name: String = "",

0 commit comments

Comments
 (0)