1use anyhow::Result;
6use std::collections::HashMap;
7use tree_sitter::{Parser, Node};
8use std::path::Path;
9
10
11
12#[derive(Debug, Clone)]
14pub struct Symbol {
15 pub name: String,
16 pub kind: SymbolKind,
17 pub range: Range,
18 pub children: Vec<Symbol>,
19}
20
21#[derive(Debug, Clone, PartialEq)]
22pub enum SymbolKind {
23 Function,
24 Struct,
25 Enum,
26 Impl,
27 Mod,
28 Const,
29 Static,
30 Trait,
31 Type,
32 Variable,
33}
34
35#[derive(Debug, Clone)]
36pub struct Range {
37 pub start_line: usize,
38 pub start_col: usize,
39 pub end_line: usize,
40 pub end_col: usize,
41}
42
43pub struct SemanticAnalyzer {
45 parser: Parser,
46 symbol_table: HashMap<String, Vec<Symbol>>,
47}
48
49impl SemanticAnalyzer {
50 pub fn new() -> Result<Self> {
52 let mut parser = Parser::new();
53 let language = tree_sitter_rust::LANGUAGE.into();
54 parser.set_language(&language)?;
55
56 Ok(Self {
57 parser,
58 symbol_table: HashMap::new(),
59 })
60 }
61
62 pub fn analyze_file(&mut self, file_path: &Path, source: &str) -> Result<Vec<Symbol>> {
64 let tree = self.parser.parse(source, None)
65 .ok_or_else(|| anyhow::anyhow!("Failed to parse file"))?;
66
67 let root = tree.root_node();
68 let symbols = self.extract_symbols(root, source)?;
69
70 self.symbol_table.insert(
72 file_path.to_string_lossy().to_string(),
73 symbols.clone()
74 );
75
76 Ok(symbols)
77 }
78
79 fn extract_symbols(&self, node: Node, source: &str) -> Result<Vec<Symbol>> {
81 let mut symbols = Vec::new();
82 let mut cursor = node.walk();
83
84 for child in node.children(&mut cursor) {
85 if let Some(symbol) = self.node_to_symbol(child, source)? {
86 symbols.push(symbol);
87 }
88 }
89
90 Ok(symbols)
91 }
92
93 fn node_to_symbol(&self, node: Node, source: &str) -> Result<Option<Symbol>> {
95 let kind_str = node.kind();
96
97 let kind = match kind_str {
98 "function_item" => SymbolKind::Function,
99 "struct_item" => SymbolKind::Struct,
100 "enum_item" => SymbolKind::Enum,
101 "impl_item" => SymbolKind::Impl,
102 "mod_item" => SymbolKind::Mod,
103 "const_item" => SymbolKind::Const,
104 "static_item" => SymbolKind::Static,
105 "trait_item" => SymbolKind::Trait,
106 "type_item" => SymbolKind::Type,
107 _ => return Ok(None),
108 };
109
110 let name = self.extract_name(node, source)?;
112
113 let range = Range {
115 start_line: node.start_position().row + 1,
116 start_col: node.start_position().column,
117 end_line: node.end_position().row + 1,
118 end_col: node.end_position().column,
119 };
120
121 let children = self.extract_symbols(node, source)?;
123
124 Ok(Some(Symbol {
125 name,
126 kind,
127 range,
128 children,
129 }))
130 }
131
132 fn extract_name(&self, node: Node, source: &str) -> Result<String> {
134 let mut cursor = node.walk();
136 for child in node.children(&mut cursor) {
137 if child.kind() == "identifier" {
138 let start = child.start_byte();
139 let end = child.end_byte();
140 return Ok(source[start..end].to_string());
141 }
142 }
143 Ok("(anonymous)".to_string())
144 }
145
146 pub fn find_symbol_at_position(
148 &self,
149 file_path: &Path,
150 line: usize,
151 column: usize
152 ) -> Option<&Symbol> {
153 let symbols = self.symbol_table.get(&file_path.to_string_lossy().to_string())?;
154 self.find_symbol_in_tree(symbols, line, column)
155 }
156
157 fn find_symbol_in_tree<'a>(
159 &self,
160 symbols: &'a [Symbol],
161 line: usize,
162 column: usize
163 ) -> Option<&'a Symbol> {
164 for symbol in symbols {
165 if self.contains_position(&symbol.range, line, column) {
166 if let Some(child) = self.find_symbol_in_tree(&symbol.children, line, column) {
168 return Some(child);
169 }
170 return Some(symbol);
171 }
172 }
173 None
174 }
175
176 fn contains_position(&self, range: &Range, line: usize, column: usize) -> bool {
178 if line < range.start_line || line > range.end_line {
179 return false;
180 }
181 if line == range.start_line && column < range.start_col {
182 return false;
183 }
184 if line == range.end_line && column > range.end_col {
185 return false;
186 }
187 true
188 }
189
190 pub fn get_symbols(&self, file_path: &Path) -> Option<&Vec<Symbol>> {
192 self.symbol_table.get(&file_path.to_string_lossy().to_string())
193 }
194
195 pub fn detect_dx_patterns(&mut self, source: &str) -> Result<Vec<DxPattern>> {
197 let tree = self.parser.parse(source, None)
198 .ok_or_else(|| anyhow::anyhow!("Failed to parse source"))?;
199
200 let mut patterns = Vec::new();
201 let root = tree.root_node();
202
203 self.find_dx_elements(root, source, &mut patterns)?;
205
206 Ok(patterns)
207 }
208
209 fn find_dx_elements(
211 &self,
212 node: Node,
213 source: &str,
214 patterns: &mut Vec<DxPattern>
215 ) -> Result<()> {
216 if node.kind() == "jsx_element" || node.kind() == "jsx_self_closing_element" {
218 let mut cursor = node.walk();
219 for child in node.children(&mut cursor) {
220 if child.kind() == "jsx_opening_element" || child.kind() == "identifier" {
221 let start = child.start_byte();
222 let end = child.end_byte();
223 let text = &source[start..end];
224
225 if text.starts_with("dx") || text.contains("<dx") {
226 let component_name = text
227 .trim_start_matches('<')
228 .split(|c: char| c.is_whitespace() || c == '>')
229 .next()
230 .unwrap_or("")
231 .to_string();
232
233 if component_name.starts_with("dx") {
234 patterns.push(DxPattern {
235 component_name,
236 line: node.start_position().row + 1,
237 col: node.start_position().column,
238 });
239 }
240 }
241 }
242 }
243 }
244
245 let mut cursor = node.walk();
247 for child in node.children(&mut cursor) {
248 self.find_dx_elements(child, source, patterns)?;
249 }
250
251 Ok(())
252 }
253}
254
255#[derive(Debug, Clone)]
257pub struct DxPattern {
258 pub component_name: String,
259 pub line: usize,
260 pub col: usize,
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
268 fn test_analyzer_creation() {
269 let analyzer = SemanticAnalyzer::new();
270 assert!(analyzer.is_ok());
271 }
272
273 #[test]
274 fn test_rust_parsing() {
275 let mut analyzer = SemanticAnalyzer::new().unwrap();
276 let source = r#"
277 fn main() {
278 println!("Hello");
279 }
280
281 struct MyStruct {
282 field: i32,
283 }
284 "#;
285
286 let path = Path::new("test.rs");
287 let symbols = analyzer.analyze_file(path, source).unwrap();
288
289 assert!(!symbols.is_empty());
290 assert!(symbols.iter().any(|s| s.kind == SymbolKind::Function));
291 assert!(symbols.iter().any(|s| s.kind == SymbolKind::Struct));
292 }
293
294 #[test]
295 fn test_nested_symbols() {
296 let mut analyzer = SemanticAnalyzer::new().unwrap();
297 let source = r#"
298 mod my_mod {
299 struct Inner {
300 x: i32
301 }
302
303 impl Inner {
304 fn new() -> Self { Self { x: 0 } }
305 }
306 }
307 "#;
308
309 let path = Path::new("nested.rs");
310 let symbols = analyzer.analyze_file(path, source).unwrap();
311
312 let mod_symbol = symbols.iter().find(|s| s.kind == SymbolKind::Mod).unwrap();
313 assert_eq!(mod_symbol.name, "my_mod");
314 assert!(!mod_symbol.children.is_empty());
315
316 let struct_symbol = mod_symbol.children.iter().find(|s| s.kind == SymbolKind::Struct).unwrap();
317 assert_eq!(struct_symbol.name, "Inner");
318 }
319
320 #[test]
321 fn test_find_symbol_at_position() {
322 let mut analyzer = SemanticAnalyzer::new().unwrap();
323 let source = r#"
324 fn target_function() {
325 // code
326 }
327 "#;
328
329 let path = Path::new("lookup.rs");
330 analyzer.analyze_file(path, source).unwrap();
331
332 let symbol = analyzer.find_symbol_at_position(path, 2, 15);
334 assert!(symbol.is_some());
335 assert_eq!(symbol.unwrap().name, "target_function");
336
337 let symbol = analyzer.find_symbol_at_position(path, 10, 0);
339 assert!(symbol.is_none());
340 }
341}