cadi_core/atomizer/languages/
rust.rs1use crate::atomizer::{AtomizerConfig, ExtractedAtom, AtomKind};
4use crate::error::CadiResult;
5
6pub struct RustAtomizer {
8 _config: AtomizerConfig,
9}
10
11impl RustAtomizer {
12 pub fn new(config: AtomizerConfig) -> Self {
13 Self { _config: config }
14 }
15
16 #[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 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 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 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 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 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 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 #[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
175pub struct RustPatterns;
177
178impl RustPatterns {
179 pub fn is_test(attrs: &[String]) -> bool {
181 attrs.iter().any(|a| a.contains("#[test]") || a.contains("#[tokio::test]"))
182 }
183
184 pub fn is_conditional(attrs: &[String]) -> bool {
186 attrs.iter().any(|a| a.contains("#[cfg("))
187 }
188
189 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}