cadi_core/atomizer/languages/
rust.rs

1//! Rust-specific atomizer
2
3use crate::atomizer::{AtomizerConfig, ExtractedAtom, AtomKind};
4use crate::error::CadiResult;
5
6/// Rust-specific atomizer with Tree-sitter support
7pub struct RustAtomizer {
8    _config: AtomizerConfig,
9}
10
11impl RustAtomizer {
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_rust::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 Rust
30        let query_src = r#"
31            (function_item
32                name: (identifier) @fn_name
33            ) @function
34            
35            (struct_item
36                name: (type_identifier) @struct_name
37            ) @struct
38            
39            (enum_item
40                name: (type_identifier) @enum_name
41            ) @enum
42            
43            (trait_item
44                name: (type_identifier) @trait_name
45            ) @trait
46            
47            (impl_item) @impl
48        "#;
49        
50        let query = Query::new(&tree_sitter_rust::language(), query_src)?;
51        let mut cursor = QueryCursor::new();
52        
53        let matches = cursor.matches(&query, tree.root_node(), source.as_bytes());
54
55        for m in matches {
56            // Build a map of capture name -> node for this match
57            let mut caps: std::collections::HashMap<String, tree_sitter::Node> = std::collections::HashMap::new();
58            for cap in m.captures.iter() {
59                let name = query.capture_names()[cap.index as usize];
60                caps.insert(name.to_string(), cap.node);
61            }
62
63            // Function
64            if let (Some(fn_node), Some(name_node)) = (caps.get("function"), caps.get("fn_name")) {
65                let name = name_node.utf8_text(source.as_bytes()).unwrap_or("unknown").to_string();
66                let start = fn_node.start_byte();
67                let end = fn_node.end_byte();
68                let start_point = fn_node.start_position();
69                let end_point = fn_node.end_position();
70
71                atoms.push(ExtractedAtom {
72                    name: name.clone(),
73                    kind: AtomKind::Function,
74                    source: source[start..end].to_string(),
75                    start_byte: start,
76                    end_byte: end,
77                    start_line: start_point.row + 1,
78                    end_line: end_point.row + 1,
79                    defines: vec![name],
80                    references: Vec::new(),
81                    doc_comment: None,
82                    visibility: crate::atomizer::extractor::Visibility::Public,
83                    parent: None,
84                    decorators: Vec::new(),
85                });
86            }
87
88            // Struct
89            if let (Some(struct_node), Some(name_node)) = (caps.get("struct"), caps.get("struct_name")) {
90                let name = name_node.utf8_text(source.as_bytes()).unwrap_or("unknown").to_string();
91                let start = struct_node.start_byte();
92                let end = struct_node.end_byte();
93                let start_point = struct_node.start_position();
94                let end_point = struct_node.end_position();
95
96                atoms.push(ExtractedAtom {
97                    name: name.clone(),
98                    kind: AtomKind::Struct,
99                    source: source[start..end].to_string(),
100                    start_byte: start,
101                    end_byte: end,
102                    start_line: start_point.row + 1,
103                    end_line: end_point.row + 1,
104                    defines: vec![name],
105                    references: Vec::new(),
106                    doc_comment: None,
107                    visibility: crate::atomizer::extractor::Visibility::Public,
108                    parent: None,
109                    decorators: Vec::new(),
110                });
111            }
112
113            // Enum
114            if let (Some(enum_node), Some(name_node)) = (caps.get("enum"), caps.get("enum_name")) {
115                let name = name_node.utf8_text(source.as_bytes()).unwrap_or("unknown").to_string();
116                let start = enum_node.start_byte();
117                let end = enum_node.end_byte();
118                let start_point = enum_node.start_position();
119                let end_point = enum_node.end_position();
120
121                atoms.push(ExtractedAtom {
122                    name: name.clone(),
123                    kind: AtomKind::Enum,
124                    source: source[start..end].to_string(),
125                    start_byte: start,
126                    end_byte: end,
127                    start_line: start_point.row + 1,
128                    end_line: end_point.row + 1,
129                    defines: vec![name],
130                    references: Vec::new(),
131                    doc_comment: None,
132                    visibility: crate::atomizer::extractor::Visibility::Public,
133                    parent: None,
134                    decorators: Vec::new(),
135                });
136            }
137
138            // Trait
139            if let (Some(trait_node), Some(name_node)) = (caps.get("trait"), caps.get("trait_name")) {
140                let name = name_node.utf8_text(source.as_bytes()).unwrap_or("unknown").to_string();
141                let start = trait_node.start_byte();
142                let end = trait_node.end_byte();
143                let start_point = trait_node.start_position();
144                let end_point = trait_node.end_position();
145
146                atoms.push(ExtractedAtom {
147                    name: name.clone(),
148                    kind: AtomKind::Trait,
149                    source: source[start..end].to_string(),
150                    start_byte: start,
151                    end_byte: end,
152                    start_line: start_point.row + 1,
153                    end_line: end_point.row + 1,
154                    defines: vec![name],
155                    references: Vec::new(),
156                    doc_comment: None,
157                    visibility: crate::atomizer::extractor::Visibility::Public,
158                    parent: None,
159                    decorators: Vec::new(),
160                });
161            }
162        }
163
164        Ok(atoms)
165    }
166    
167    /// Fallback extraction without Tree-sitter
168    #[cfg(not(feature = "ast-parsing"))]
169    pub fn extract(&self, source: &str) -> CadiResult<Vec<ExtractedAtom>> {
170        use crate::atomizer::AtomExtractor;
171        AtomExtractor::new("rust", self.config.clone()).extract(source)
172    }
173}
174
175/// Common Rust patterns to detect
176pub struct RustPatterns;
177
178impl RustPatterns {
179    /// Check if a function is a test
180    pub fn is_test(attrs: &[String]) -> bool {
181        attrs.iter().any(|a| a.contains("#[test]") || a.contains("#[tokio::test]"))
182    }
183    
184    /// Check if code is behind a cfg attribute
185    pub fn is_conditional(attrs: &[String]) -> bool {
186        attrs.iter().any(|a| a.contains("#[cfg("))
187    }
188    
189    /// Extract visibility from source
190    pub fn parse_visibility(source: &str) -> &'static str {
191        if source.contains("pub(crate)") {
192            "crate"
193        } else if source.contains("pub(super)") {
194            "super"
195        } else if source.starts_with("pub ") || source.contains(" pub ") {
196            "public"
197        } else {
198            "private"
199        }
200    }
201}