cadi_core/atomizer/languages/
csharp.rs

1//! C#-specific atomizer
2
3use crate::atomizer::{AtomizerConfig, ExtractedAtom, AtomKind};
4use crate::error::CadiResult;
5
6/// C#-specific atomizer with Tree-sitter support
7pub struct CSharpAtomizer {
8    _config: AtomizerConfig,
9}
10
11impl CSharpAtomizer {
12    pub fn new(config: AtomizerConfig) -> Self {
13        Self { _config: config }
14    }
15
16    /// Extract atoms using Tree-sitter (when feature enabled)
17    #[cfg(feature = "ast-parsing")]
18    pub fn extract(&self, source: &str) -> CadiResult<Vec<ExtractedAtom>> {
19        use tree_sitter::{Parser, Query, QueryCursor};
20        
21        let mut parser = Parser::new();
22        parser.set_language(&tree_sitter_c_sharp::language())?;
23        
24        let tree = parser.parse(source, None)
25            .ok_or_else(|| crate::error::CadiError::AtomizerError("Parse failed".into()))?;
26        
27        let mut atoms = Vec::new();
28        
29        // Tree-sitter queries for C#
30        let query_src = r#"
31            (method_declaration
32                name: (identifier) @method_name
33            ) @method
34            
35            (class_declaration
36                name: (identifier) @class_name
37            ) @class
38            
39            (interface_declaration
40                name: (identifier) @interface_name
41            ) @interface
42            
43            (struct_declaration
44                name: (identifier) @struct_name
45            ) @struct
46            
47            (enum_declaration
48                name: (identifier) @enum_name
49            ) @enum
50            
51            (namespace_declaration
52                name: (_) @namespace_name
53            ) @namespace
54        "#;
55        
56        let query = Query::new(&tree_sitter_c_sharp::language(), query_src)?;
57        let mut cursor = QueryCursor::new();
58        
59        let matches = cursor.matches(&query, tree.root_node(), source.as_bytes());
60        
61        for m in matches {
62            let mut name = "unknown".to_string();
63            let mut kind = AtomKind::Method;
64            let mut atom_node = None;
65
66            for capture in m.captures {
67                let capture_name = query.capture_names()[capture.index as usize];
68                match capture_name {
69                    "method_name" | "class_name" | "interface_name" | "struct_name" | "enum_name" | "namespace_name" => {
70                        name = capture.node.utf8_text(source.as_bytes()).unwrap_or("unknown").to_string();
71                    }
72                    "method" => {
73                        kind = AtomKind::Method;
74                        atom_node = Some(capture.node);
75                    }
76                    "class" => {
77                        kind = AtomKind::Class;
78                        atom_node = Some(capture.node);
79                    }
80                    "interface" => {
81                        kind = AtomKind::Interface;
82                        atom_node = Some(capture.node);
83                    }
84                    "struct" => {
85                        kind = AtomKind::Struct;
86                        atom_node = Some(capture.node);
87                    }
88                    "enum" => {
89                        kind = AtomKind::Enum;
90                        atom_node = Some(capture.node);
91                    }
92                    "namespace" => {
93                        kind = AtomKind::Module;
94                        atom_node = Some(capture.node);
95                    }
96                    _ => {}
97                }
98            }
99
100            if let Some(node) = atom_node {
101                let start_byte = node.start_byte();
102                let end_byte = node.end_byte();
103                let start_point = node.start_position();
104                let end_point = node.end_position();
105
106                atoms.push(ExtractedAtom {
107                    name,
108                    kind,
109                    source: source[start_byte..end_byte].to_string(),
110                    start_byte,
111                    end_byte,
112                    start_line: start_point.row + 1,
113                    end_line: end_point.row + 1,
114                    defines: vec![],
115                    references: Vec::new(),
116                    doc_comment: None,
117                    visibility: crate::atomizer::extractor::Visibility::Public,
118                    parent: None,
119                    decorators: Vec::new(),
120                });
121            }
122        }
123        
124        Ok(atoms)
125    }
126    
127    /// Fallback extraction without Tree-sitter
128    #[cfg(not(feature = "ast-parsing"))]
129    pub fn extract(&self, source: &str) -> CadiResult<Vec<ExtractedAtom>> {
130        use crate::atomizer::AtomExtractor;
131        AtomExtractor::new("csharp", self._config.clone()).extract(source)
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138    use crate::atomizer::AtomizerConfig;
139
140    #[test]
141    fn test_csharp_extraction() {
142        let source = r#"
143            namespace MyNamespace {
144                public class Greeter {
145                    public void Greet(string name) {
146                        Console.WriteLine("Hello " + name);
147                    }
148                }
149            }
150        "#;
151
152        let atomizer = CSharpAtomizer::new(AtomizerConfig::default());
153        let atoms = atomizer.extract(source).unwrap();
154
155        // Should find class Greeter and method Greet
156        assert!(atoms.iter().any(|a| a.name == "Greeter"));
157        assert!(atoms.iter().any(|a| a.name == "Greet"));
158    }
159}