1use anyhow::{Context, Result};
17use streaming_iterator::StreamingIterator;
18use tree_sitter::{Parser, Query, QueryCursor};
19use crate::models::{Language, SearchResult, Span, SymbolKind};
20use crate::parsers::{DependencyExtractor, ImportInfo};
21
22pub fn parse(path: &str, source: &str) -> Result<Vec<SearchResult>> {
24 let mut parser = Parser::new();
25 let language = tree_sitter_rust::LANGUAGE;
26
27 parser
28 .set_language(&language.into())
29 .context("Failed to set Rust language")?;
30
31 let tree = parser
32 .parse(source, None)
33 .context("Failed to parse Rust source")?;
34
35 let root_node = tree.root_node();
36
37 let mut symbols = Vec::new();
38
39 symbols.extend(extract_functions(source, &root_node)?);
41 symbols.extend(extract_structs(source, &root_node)?);
42 symbols.extend(extract_enums(source, &root_node)?);
43 symbols.extend(extract_traits(source, &root_node)?);
44 symbols.extend(extract_impls(source, &root_node)?);
45 symbols.extend(extract_constants(source, &root_node)?);
46 symbols.extend(extract_statics(source, &root_node)?);
47 symbols.extend(extract_local_variables(source, &root_node)?);
48 symbols.extend(extract_modules(source, &root_node)?);
49 symbols.extend(extract_type_aliases(source, &root_node)?);
50 symbols.extend(extract_macros(source, &root_node)?);
51 symbols.extend(extract_attributes(source, &root_node)?);
52
53
54 for symbol in &mut symbols {
56 symbol.path = path.to_string();
57 symbol.lang = Language::Rust;
58 }
59
60 Ok(symbols)
61}
62
63fn extract_functions(source: &str, root: &tree_sitter::Node) -> Result<Vec<SearchResult>> {
65 let language = tree_sitter_rust::LANGUAGE;
66 let query_str = r#"
67 (function_item
68 name: (identifier) @name) @function
69 "#;
70
71 let query = Query::new(&language.into(), query_str)
72 .context("Failed to create function query")?;
73
74 extract_symbols(source, root, &query, SymbolKind::Function, None)
75}
76
77fn extract_structs(source: &str, root: &tree_sitter::Node) -> Result<Vec<SearchResult>> {
79 let language = tree_sitter_rust::LANGUAGE;
80 let query_str = r#"
81 (struct_item
82 name: (type_identifier) @name) @struct
83 "#;
84
85 let query = Query::new(&language.into(), query_str)
86 .context("Failed to create struct query")?;
87
88 extract_symbols(source, root, &query, SymbolKind::Struct, None)
89}
90
91fn extract_enums(source: &str, root: &tree_sitter::Node) -> Result<Vec<SearchResult>> {
93 let language = tree_sitter_rust::LANGUAGE;
94 let query_str = r#"
95 (enum_item
96 name: (type_identifier) @name) @enum
97 "#;
98
99 let query = Query::new(&language.into(), query_str)
100 .context("Failed to create enum query")?;
101
102 extract_symbols(source, root, &query, SymbolKind::Enum, None)
103}
104
105fn extract_traits(source: &str, root: &tree_sitter::Node) -> Result<Vec<SearchResult>> {
107 let language = tree_sitter_rust::LANGUAGE;
108 let query_str = r#"
109 (trait_item
110 name: (type_identifier) @name) @trait
111 "#;
112
113 let query = Query::new(&language.into(), query_str)
114 .context("Failed to create trait query")?;
115
116 extract_symbols(source, root, &query, SymbolKind::Trait, None)
117}
118
119fn extract_impls(source: &str, root: &tree_sitter::Node) -> Result<Vec<SearchResult>> {
121 let language = tree_sitter_rust::LANGUAGE;
122
123 let query_str = r#"
125 (impl_item
126 type: (type_identifier) @impl_name
127 body: (declaration_list
128 (function_item
129 name: (identifier) @method_name))) @impl
130 "#;
131
132 let query = Query::new(&language.into(), query_str)
133 .context("Failed to create impl query")?;
134
135 let mut cursor = QueryCursor::new();
136 let mut matches = cursor.matches(&query, *root, source.as_bytes());
137
138 let mut symbols = Vec::new();
139
140 while let Some(match_) = matches.next() {
141 let mut impl_name = None;
142 let mut method_name = None;
143 let mut method_node = None;
144
145 for capture in match_.captures {
146 let capture_name: &str = &query.capture_names()[capture.index as usize];
147 match capture_name {
148 "impl_name" => {
149 impl_name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
150 }
151 "method_name" => {
152 method_name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
153 let mut current = capture.node;
155 while let Some(parent) = current.parent() {
156 if parent.kind() == "function_item" {
157 method_node = Some(parent);
158 break;
159 }
160 current = parent;
161 }
162 }
163 _ => {}
164 }
165 }
166
167 if let (Some(impl_name), Some(method_name), Some(node)) = (impl_name, method_name, method_node) {
168 let scope = format!("impl {}", impl_name);
169 let span = node_to_span(&node);
170 let preview = extract_preview(source, &span);
171
172 symbols.push(SearchResult::new(
173 String::new(), Language::Rust,
175 SymbolKind::Method,
176 Some(method_name),
177 span,
178 Some(scope),
179 preview,
180 ));
181 }
182 }
183
184 Ok(symbols)
185}
186
187fn extract_constants(source: &str, root: &tree_sitter::Node) -> Result<Vec<SearchResult>> {
189 let language = tree_sitter_rust::LANGUAGE;
190 let query_str = r#"
191 (const_item
192 name: (identifier) @name) @const
193 "#;
194
195 let query = Query::new(&language.into(), query_str)
196 .context("Failed to create const query")?;
197
198 extract_symbols(source, root, &query, SymbolKind::Constant, None)
199}
200
201fn extract_statics(source: &str, root: &tree_sitter::Node) -> Result<Vec<SearchResult>> {
203 let language = tree_sitter_rust::LANGUAGE;
204 let query_str = r#"
205 (static_item
206 name: (identifier) @name) @static
207 "#;
208
209 let query = Query::new(&language.into(), query_str)
210 .context("Failed to create static query")?;
211
212 extract_symbols(source, root, &query, SymbolKind::Variable, None)
213}
214
215fn extract_local_variables(source: &str, root: &tree_sitter::Node) -> Result<Vec<SearchResult>> {
217 let language = tree_sitter_rust::LANGUAGE;
218 let query_str = r#"
219 (let_declaration
220 pattern: (identifier) @name) @let
221 "#;
222
223 let query = Query::new(&language.into(), query_str)
224 .context("Failed to create let declaration query")?;
225
226 extract_symbols(source, root, &query, SymbolKind::Variable, None)
227}
228
229fn extract_modules(source: &str, root: &tree_sitter::Node) -> Result<Vec<SearchResult>> {
231 let language = tree_sitter_rust::LANGUAGE;
232 let query_str = r#"
233 (mod_item
234 name: (identifier) @name) @module
235 "#;
236
237 let query = Query::new(&language.into(), query_str)
238 .context("Failed to create module query")?;
239
240 extract_symbols(source, root, &query, SymbolKind::Module, None)
241}
242
243fn extract_type_aliases(source: &str, root: &tree_sitter::Node) -> Result<Vec<SearchResult>> {
245 let language = tree_sitter_rust::LANGUAGE;
246 let query_str = r#"
247 (type_item
248 name: (type_identifier) @name) @type
249 "#;
250
251 let query = Query::new(&language.into(), query_str)
252 .context("Failed to create type query")?;
253
254 extract_symbols(source, root, &query, SymbolKind::Type, None)
255}
256
257fn extract_macros(source: &str, root: &tree_sitter::Node) -> Result<Vec<SearchResult>> {
259 let language = tree_sitter_rust::LANGUAGE;
260 let query_str = r#"
261 (macro_definition
262 name: (identifier) @name) @macro
263 "#;
264
265 let query = Query::new(&language.into(), query_str)
266 .context("Failed to create macro query")?;
267
268 extract_symbols(source, root, &query, SymbolKind::Macro, None)
269}
270
271fn extract_attributes(source: &str, root: &tree_sitter::Node) -> Result<Vec<SearchResult>> {
275 let language = tree_sitter_rust::LANGUAGE;
276 let mut symbols = Vec::new();
277
278 let func_query_str = r#"
280 (function_item
281 name: (identifier) @name) @function
282 "#;
283
284 let func_query = Query::new(&language.into(), func_query_str)
285 .context("Failed to create function query")?;
286
287 let mut cursor = QueryCursor::new();
288 let mut matches = cursor.matches(&func_query, *root, source.as_bytes());
289
290 while let Some(match_) = matches.next() {
291 let mut name = None;
292 let mut func_node = None;
293
294 for capture in match_.captures {
295 let capture_name: &str = &func_query.capture_names()[capture.index as usize];
296 match capture_name {
297 "name" => {
298 name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
299 }
300 "function" => {
301 func_node = Some(capture.node);
302 }
303 _ => {}
304 }
305 }
306
307 if let (Some(name), Some(func_node)) = (name, func_node) {
309 let mut has_proc_macro_attr = false;
310
311 if let Some(parent) = func_node.parent() {
312 let mut func_index = None;
313 for i in 0..parent.child_count() {
314 if let Some(child) = parent.child(i) {
315 if child.id() == func_node.id() {
316 func_index = Some(i);
317 break;
318 }
319 }
320 }
321
322 if let Some(func_idx) = func_index {
323 for i in (0..func_idx).rev() {
324 if let Some(child) = parent.child(i) {
325 if child.kind() == "attribute_item" {
326 let attr_text = child.utf8_text(source.as_bytes()).unwrap_or("");
327 if attr_text.contains("proc_macro_attribute") {
328 has_proc_macro_attr = true;
329 }
330 } else if !child.kind().contains("comment") && child.kind() != "line_comment" {
331 break;
332 }
333 }
334 }
335 }
336 }
337
338 if has_proc_macro_attr {
339 let span = node_to_span(&func_node);
340 let preview = extract_preview(source, &span);
341
342 symbols.push(SearchResult::new(
343 String::new(),
344 Language::Rust,
345 SymbolKind::Attribute,
346 Some(name),
347 span,
348 None,
349 preview,
350 ));
351 }
352 }
353 }
354
355 let attr_query_str = r#"
357 (attribute_item
358 (attribute
359 (identifier) @attr_name)) @attr
360 "#;
361
362 let attr_query = Query::new(&language.into(), attr_query_str)
363 .context("Failed to create attribute use query")?;
364
365 let mut cursor = QueryCursor::new();
366 let mut matches = cursor.matches(&attr_query, *root, source.as_bytes());
367
368 while let Some(match_) = matches.next() {
369 let mut attr_name = None;
370 let mut attr_node = None;
371
372 for capture in match_.captures {
373 let capture_name: &str = &attr_query.capture_names()[capture.index as usize];
374 match capture_name {
375 "attr_name" => {
376 attr_name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
377 }
378 "attr" => {
379 attr_node = Some(capture.node);
380 }
381 _ => {}
382 }
383 }
384
385 if let (Some(name), Some(node)) = (attr_name, attr_node) {
386 let span = node_to_span(&node);
387 let preview = extract_preview(source, &span);
388
389 symbols.push(SearchResult::new(
390 String::new(),
391 Language::Rust,
392 SymbolKind::Attribute,
393 Some(name),
394 span,
395 None,
396 preview,
397 ));
398 }
399 }
400
401 Ok(symbols)
402}
403
404fn extract_symbols(
406 source: &str,
407 root: &tree_sitter::Node,
408 query: &Query,
409 kind: SymbolKind,
410 scope: Option<String>,
411) -> Result<Vec<SearchResult>> {
412 let mut cursor = QueryCursor::new();
413 let mut matches = cursor.matches(query, *root, source.as_bytes());
414
415 let mut symbols = Vec::new();
416
417 while let Some(match_) = matches.next() {
418 let mut name = None;
420 let mut full_node = None;
421
422 for capture in match_.captures {
423 let capture_name: &str = &query.capture_names()[capture.index as usize];
424 if capture_name == "name" {
425 name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
426 } else {
427 full_node = Some(capture.node);
429 }
430 }
431
432 if let (Some(name), Some(node)) = (name, full_node) {
433 let span = node_to_span(&node);
434 let preview = extract_preview(source, &span);
435
436 symbols.push(SearchResult::new(
437 String::new(), Language::Rust,
439 kind.clone(),
440 Some(name),
441 span,
442 scope.clone(),
443 preview,
444 ));
445 }
446 }
447
448 Ok(symbols)
449}
450
451fn node_to_span(node: &tree_sitter::Node) -> Span {
453 let start = node.start_position();
454 let end = node.end_position();
455
456 Span::new(
457 start.row + 1, start.column,
459 end.row + 1,
460 end.column,
461 )
462}
463
464fn extract_preview(source: &str, span: &Span) -> String {
466 let lines: Vec<&str> = source.lines().collect();
467
468 let start_idx = (span.start_line - 1) as usize; let end_idx = (start_idx + 7).min(lines.len());
472
473 lines[start_idx..end_idx].join("\n")
474}
475
476pub struct RustDependencyExtractor;
478
479impl DependencyExtractor for RustDependencyExtractor {
480 fn extract_dependencies(source: &str) -> Result<Vec<ImportInfo>> {
481 let mut parser = Parser::new();
482 let language = tree_sitter_rust::LANGUAGE;
483
484 parser
485 .set_language(&language.into())
486 .context("Failed to set Rust language")?;
487
488 let tree = parser
489 .parse(source, None)
490 .context("Failed to parse Rust source")?;
491
492 let root_node = tree.root_node();
493
494 let mut imports = Vec::new();
495
496 imports.extend(extract_use_declarations(source, &root_node)?);
498
499 imports.extend(extract_mod_items(source, &root_node)?);
501
502 imports.extend(extract_extern_crates(source, &root_node)?);
504
505 Ok(imports)
506 }
507}
508
509fn extract_use_declarations(source: &str, root: &tree_sitter::Node) -> Result<Vec<ImportInfo>> {
511 let language = tree_sitter_rust::LANGUAGE;
512 let query_str = r#"
513 (use_declaration) @use
514 "#;
515
516 let query = Query::new(&language.into(), query_str)
517 .context("Failed to create use declaration query")?;
518
519 let mut cursor = QueryCursor::new();
520 let mut matches = cursor.matches(&query, *root, source.as_bytes());
521
522 let mut imports = Vec::new();
523
524 while let Some(match_) = matches.next() {
525 for capture in match_.captures {
526 let node = capture.node;
527 let text = node.utf8_text(source.as_bytes()).unwrap_or("");
528 let line_number = node.start_position().row + 1;
529
530 let path_info = parse_rust_use_declaration(text);
532
533 for (path, symbols) in path_info {
534 let import_type = classify_rust_import(&path);
535
536 imports.push(ImportInfo {
537 imported_path: path,
538 import_type,
539 line_number,
540 imported_symbols: symbols,
541 });
542 }
543 }
544 }
545
546 Ok(imports)
547}
548
549fn extract_mod_items(source: &str, root: &tree_sitter::Node) -> Result<Vec<ImportInfo>> {
551 let language = tree_sitter_rust::LANGUAGE;
552 let query_str = r#"
553 (mod_item
554 name: (identifier) @name) @mod
555 "#;
556
557 let query = Query::new(&language.into(), query_str)
558 .context("Failed to create mod item query")?;
559
560 let mut cursor = QueryCursor::new();
561 let mut matches = cursor.matches(&query, *root, source.as_bytes());
562
563 let mut imports = Vec::new();
564
565 while let Some(match_) = matches.next() {
566 let mut name = None;
567 let mut mod_node = None;
568
569 for capture in match_.captures {
570 let capture_name: &str = &query.capture_names()[capture.index as usize];
571 match capture_name {
572 "name" => {
573 name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
574 }
575 "mod" => {
576 mod_node = Some(capture.node);
577 }
578 _ => {}
579 }
580 }
581
582 if let (Some(name), Some(node)) = (name, mod_node) {
583 let has_body = node.child_by_field_name("body").is_some();
585
586 if !has_body {
587 let line_number = node.start_position().row + 1;
589
590 imports.push(ImportInfo {
591 imported_path: name,
592 import_type: crate::models::ImportType::Internal,
593 line_number,
594 imported_symbols: None,
595 });
596 }
597 }
598 }
599
600 Ok(imports)
601}
602
603fn extract_extern_crates(source: &str, root: &tree_sitter::Node) -> Result<Vec<ImportInfo>> {
605 let language = tree_sitter_rust::LANGUAGE;
606 let query_str = r#"
607 (extern_crate_declaration
608 name: (identifier) @name) @extern
609 "#;
610
611 let query = Query::new(&language.into(), query_str)
612 .context("Failed to create extern crate query")?;
613
614 let mut cursor = QueryCursor::new();
615 let mut matches = cursor.matches(&query, *root, source.as_bytes());
616
617 let mut imports = Vec::new();
618
619 while let Some(match_) = matches.next() {
620 let mut name = None;
621 let mut extern_node = None;
622
623 for capture in match_.captures {
624 let capture_name: &str = &query.capture_names()[capture.index as usize];
625 match capture_name {
626 "name" => {
627 name = Some(capture.node.utf8_text(source.as_bytes()).unwrap_or("").to_string());
628 }
629 "extern" => {
630 extern_node = Some(capture.node);
631 }
632 _ => {}
633 }
634 }
635
636 if let (Some(name), Some(node)) = (name, extern_node) {
637 let line_number = node.start_position().row + 1;
638 let import_type = classify_rust_import(&name);
639
640 imports.push(ImportInfo {
641 imported_path: name,
642 import_type,
643 line_number,
644 imported_symbols: None,
645 });
646 }
647 }
648
649 Ok(imports)
650}
651
652fn classify_rust_import(path: &str) -> crate::models::ImportType {
654 use crate::models::ImportType;
655
656 if path.starts_with("std::") || path.starts_with("core::") || path.starts_with("alloc::") {
657 ImportType::Stdlib
658 } else if path.starts_with("crate::") || path.starts_with("super::") || path.starts_with("self::") {
659 ImportType::Internal
660 } else {
661 ImportType::External
663 }
664}
665
666fn parse_rust_use_declaration(text: &str) -> Vec<(String, Option<Vec<String>>)> {
675 let text = text.trim()
677 .strip_prefix("pub(crate)").unwrap_or(text)
678 .trim()
679 .strip_prefix("pub(super)").unwrap_or(text)
680 .trim()
681 .strip_prefix("pub").unwrap_or(text)
682 .trim()
683 .strip_prefix("use").unwrap_or(text)
684 .trim()
685 .strip_suffix(";").unwrap_or(text)
686 .trim();
687
688 if text.contains('{') {
690 if let Some(idx) = text.find('{') {
692 let base_path = text[..idx].trim_end_matches("::").to_string();
693
694 if let Some(end) = text.find('}') {
695 let symbols_str = &text[idx + 1..end];
696 let symbols: Vec<String> = symbols_str
697 .split(',')
698 .map(|s| {
699 let trimmed = s.trim();
701 if let Some(as_idx) = trimmed.find(" as ") {
702 trimmed[..as_idx].trim().to_string()
703 } else {
704 trimmed.to_string()
705 }
706 })
707 .filter(|s| !s.is_empty() && s != "*")
708 .collect();
709
710 if !symbols.is_empty() {
711 return vec![(base_path, Some(symbols))];
712 }
713 }
714 }
715 }
716
717 let path = if let Some(as_idx) = text.find(" as ") {
719 text[..as_idx].trim().to_string()
720 } else {
721 text.to_string()
722 };
723
724 vec![(path, None)]
725}
726
727#[cfg(test)]
728mod tests {
729 use super::*;
730
731 #[test]
732 fn test_parse_function() {
733 let source = r#"
734 fn hello_world() {
735 println!("Hello, world!");
736 }
737 "#;
738
739 let symbols = parse("test.rs", source).unwrap();
740 assert_eq!(symbols.len(), 1);
741 assert_eq!(symbols[0].symbol.as_deref(), Some("hello_world"));
742 assert!(matches!(symbols[0].kind, SymbolKind::Function));
743 }
744
745 #[test]
746 fn test_parse_struct() {
747 let source = r#"
748 struct User {
749 name: String,
750 age: u32,
751 }
752 "#;
753
754 let symbols = parse("test.rs", source).unwrap();
755 assert_eq!(symbols.len(), 1);
756 assert_eq!(symbols[0].symbol.as_deref(), Some("User"));
757 assert!(matches!(symbols[0].kind, SymbolKind::Struct));
758 }
759
760 #[test]
761 fn test_parse_impl() {
762 let source = r#"
763 struct User {
764 name: String,
765 }
766
767 impl User {
768 fn new(name: String) -> Self {
769 User { name }
770 }
771
772 fn get_name(&self) -> &str {
773 &self.name
774 }
775 }
776 "#;
777
778 let symbols = parse("test.rs", source).unwrap();
779
780 assert!(symbols.len() >= 3);
782
783 let method_symbols: Vec<_> = symbols.iter()
784 .filter(|s| matches!(s.kind, SymbolKind::Method))
785 .collect();
786
787 assert_eq!(method_symbols.len(), 2);
788 assert!(method_symbols.iter().any(|s| s.symbol.as_deref() == Some("new")));
789 assert!(method_symbols.iter().any(|s| s.symbol.as_deref() == Some("get_name")));
790
791 }
794
795 #[test]
796 fn test_parse_enum() {
797 let source = r#"
798 enum Status {
799 Active,
800 Inactive,
801 }
802 "#;
803
804 let symbols = parse("test.rs", source).unwrap();
805 assert_eq!(symbols.len(), 1);
806 assert_eq!(symbols[0].symbol.as_deref(), Some("Status"));
807 assert!(matches!(symbols[0].kind, SymbolKind::Enum));
808 }
809
810 #[test]
811 fn test_parse_trait() {
812 let source = r#"
813 trait Drawable {
814 fn draw(&self);
815 }
816 "#;
817
818 let symbols = parse("test.rs", source).unwrap();
819 assert_eq!(symbols.len(), 1);
820 assert_eq!(symbols[0].symbol.as_deref(), Some("Drawable"));
821 assert!(matches!(symbols[0].kind, SymbolKind::Trait));
822 }
823
824 #[test]
825 fn test_parse_multiple_symbols() {
826 let source = r#"
827 const MAX_SIZE: usize = 100;
828
829 struct Config {
830 size: usize,
831 }
832
833 fn create_config() -> Config {
834 Config { size: MAX_SIZE }
835 }
836 "#;
837
838 let symbols = parse("test.rs", source).unwrap();
839
840 assert_eq!(symbols.len(), 3);
842
843 let kinds: Vec<&SymbolKind> = symbols.iter().map(|s| &s.kind).collect();
844 assert!(kinds.contains(&&SymbolKind::Constant));
845 assert!(kinds.contains(&&SymbolKind::Struct));
846 assert!(kinds.contains(&&SymbolKind::Function));
847 }
848
849 #[test]
850 fn test_local_variables_included() {
851 let source = r#"
852 fn calculate(input: i32) -> i32 {
853 let local_var = input * 2;
854 let result = local_var + 10;
855 result
856 }
857
858 struct Calculator;
859
860 impl Calculator {
861 fn compute(&self, value: i32) -> i32 {
862 let temp = value * 3;
863 let mut final_value = temp + 5;
864 final_value += 1;
865 final_value
866 }
867 }
868 "#;
869
870 let symbols = parse("test.rs", source).unwrap();
871
872 let variables: Vec<_> = symbols.iter()
874 .filter(|s| matches!(s.kind, SymbolKind::Variable))
875 .collect();
876
877 assert!(variables.iter().any(|v| v.symbol.as_deref() == Some("local_var")));
879 assert!(variables.iter().any(|v| v.symbol.as_deref() == Some("result")));
880 assert!(variables.iter().any(|v| v.symbol.as_deref() == Some("temp")));
881 assert!(variables.iter().any(|v| v.symbol.as_deref() == Some("final_value")));
882
883 }
885
886 #[test]
887 fn test_static_variables() {
888 let source = r#"
889 static GLOBAL_COUNTER: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
890 static mut MUTABLE_GLOBAL: i32 = 0;
891
892 const MAX_SIZE: usize = 100;
893
894 fn increment() {
895 GLOBAL_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
896 }
897 "#;
898
899 let symbols = parse("test.rs", source).unwrap();
900
901 let statics: Vec<_> = symbols.iter()
903 .filter(|s| matches!(s.kind, SymbolKind::Variable))
904 .collect();
905
906 let constants: Vec<_> = symbols.iter()
907 .filter(|s| matches!(s.kind, SymbolKind::Constant))
908 .collect();
909
910 assert!(statics.iter().any(|v| v.symbol.as_deref() == Some("GLOBAL_COUNTER")));
912 assert!(statics.iter().any(|v| v.symbol.as_deref() == Some("MUTABLE_GLOBAL")));
913
914 assert!(constants.iter().any(|c| c.symbol.as_deref() == Some("MAX_SIZE")));
916 }
917
918 #[test]
919 fn test_macros() {
920 let source = r#"
921 macro_rules! say_hello {
922 () => {
923 println!("Hello!");
924 };
925 }
926
927 macro_rules! vec_of_strings {
928 ($($x:expr),*) => {
929 vec![$($x.to_string()),*]
930 };
931 }
932
933 fn main() {
934 say_hello!();
935 }
936 "#;
937
938 let symbols = parse("test.rs", source).unwrap();
939
940 let macros: Vec<_> = symbols.iter()
942 .filter(|s| matches!(s.kind, SymbolKind::Macro))
943 .collect();
944
945 assert!(macros.iter().any(|m| m.symbol.as_deref() == Some("say_hello")));
947 assert!(macros.iter().any(|m| m.symbol.as_deref() == Some("vec_of_strings")));
948 assert_eq!(macros.len(), 2);
949 }
950
951 #[test]
952 fn test_attribute_proc_macros() {
953 let source = r#"
954 use proc_macro::TokenStream;
955
956 #[proc_macro_attribute]
957 pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream {
958 item
959 }
960
961 #[proc_macro_attribute]
962 pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
963 item
964 }
965
966 // Regular function - should NOT be captured
967 pub fn helper() {}
968 "#;
969
970 let symbols = parse("test.rs", source).unwrap();
971
972 let attributes: Vec<_> = symbols.iter()
974 .filter(|s| matches!(s.kind, SymbolKind::Attribute))
975 .collect();
976
977 assert!(attributes.iter().any(|a| a.symbol.as_deref() == Some("test")));
979 assert!(attributes.iter().any(|a| a.symbol.as_deref() == Some("route")));
980
981 assert!(!attributes.iter().any(|a| a.symbol.as_deref() == Some("helper")));
983
984 assert_eq!(attributes.len(), 4);
986 }
987
988 #[test]
989 fn test_attribute_uses() {
990 let source = r#"
991 #[test]
992 fn test_something() {
993 assert_eq!(1, 1);
994 }
995
996 #[test]
997 #[should_panic]
998 fn test_panic() {
999 panic!("expected");
1000 }
1001
1002 #[derive(Debug, Clone)]
1003 struct MyStruct {
1004 field: i32
1005 }
1006
1007 #[cfg(test)]
1008 mod tests {
1009 #[test]
1010 fn nested_test() {}
1011 }
1012 "#;
1013
1014 let symbols = parse("test.rs", source).unwrap();
1015
1016 let attributes: Vec<_> = symbols.iter()
1018 .filter(|s| matches!(s.kind, SymbolKind::Attribute))
1019 .collect();
1020
1021 assert!(attributes.iter().any(|a| a.symbol.as_deref() == Some("test")));
1023 assert!(attributes.iter().any(|a| a.symbol.as_deref() == Some("should_panic")));
1024 assert!(attributes.iter().any(|a| a.symbol.as_deref() == Some("derive")));
1025 assert!(attributes.iter().any(|a| a.symbol.as_deref() == Some("cfg")));
1026
1027 assert_eq!(attributes.len(), 6);
1029 }
1030
1031 #[test]
1032 fn test_extract_dependencies_use_declarations() {
1033 let source = r#"
1034 use std::collections::HashMap;
1035 use crate::models::{Language, SearchResult};
1036 use super::utils;
1037 use anyhow::Result;
1038 "#;
1039
1040 let deps = RustDependencyExtractor::extract_dependencies(source).unwrap();
1041
1042 assert_eq!(deps.len(), 4);
1044
1045 let std_import = deps.iter().find(|d| d.imported_path == "std::collections::HashMap").unwrap();
1047 assert!(matches!(std_import.import_type, crate::models::ImportType::Stdlib));
1048
1049 let crate_import = deps.iter().find(|d| d.imported_path == "crate::models").unwrap();
1051 assert!(matches!(crate_import.import_type, crate::models::ImportType::Internal));
1052 assert!(crate_import.imported_symbols.is_some());
1053 let symbols = crate_import.imported_symbols.as_ref().unwrap();
1054 assert_eq!(symbols.len(), 2);
1055 assert!(symbols.contains(&"Language".to_string()));
1056 assert!(symbols.contains(&"SearchResult".to_string()));
1057
1058 let super_import = deps.iter().find(|d| d.imported_path == "super::utils").unwrap();
1060 assert!(matches!(super_import.import_type, crate::models::ImportType::Internal));
1061
1062 let external_import = deps.iter().find(|d| d.imported_path == "anyhow::Result").unwrap();
1064 assert!(matches!(external_import.import_type, crate::models::ImportType::External));
1065 }
1066
1067 #[test]
1068 fn test_extract_dependencies_mod_declarations() {
1069 let source = r#"
1070 mod parser;
1071 mod utils;
1072
1073 mod inline {
1074 fn test() {}
1075 }
1076 "#;
1077
1078 let deps = RustDependencyExtractor::extract_dependencies(source).unwrap();
1079
1080 assert_eq!(deps.len(), 2);
1082 assert!(deps.iter().any(|d| d.imported_path == "parser"));
1083 assert!(deps.iter().any(|d| d.imported_path == "utils"));
1084 assert!(deps.iter().all(|d| matches!(d.import_type, crate::models::ImportType::Internal)));
1085 }
1086
1087 #[test]
1088 fn test_extract_dependencies_extern_crate() {
1089 let source = r#"
1090 extern crate serde;
1091 extern crate serde_json;
1092 "#;
1093
1094 let deps = RustDependencyExtractor::extract_dependencies(source).unwrap();
1095
1096 assert_eq!(deps.len(), 2);
1098 assert!(deps.iter().any(|d| d.imported_path == "serde"));
1099 assert!(deps.iter().any(|d| d.imported_path == "serde_json"));
1100 assert!(deps.iter().all(|d| matches!(d.import_type, crate::models::ImportType::External)));
1101 }
1102
1103 #[test]
1104 fn test_parse_use_with_aliases() {
1105 let source = r#"
1106 use std::io::Result as IoResult;
1107 use std::collections::{HashMap as Map, HashSet};
1108 "#;
1109
1110 let deps = RustDependencyExtractor::extract_dependencies(source).unwrap();
1111
1112 let io_import = deps.iter().find(|d| d.imported_path == "std::io::Result").unwrap();
1114 assert!(matches!(io_import.import_type, crate::models::ImportType::Stdlib));
1115
1116 let collections_import = deps.iter().find(|d| d.imported_path == "std::collections").unwrap();
1117 let symbols = collections_import.imported_symbols.as_ref().unwrap();
1118 assert_eq!(symbols.len(), 2);
1119 assert!(symbols.contains(&"HashMap".to_string()));
1120 assert!(symbols.contains(&"HashSet".to_string()));
1121 }
1122
1123 #[test]
1124 fn test_classify_rust_imports() {
1125 use crate::models::ImportType;
1126
1127 assert!(matches!(classify_rust_import("std::collections::HashMap"), ImportType::Stdlib));
1129 assert!(matches!(classify_rust_import("core::ptr"), ImportType::Stdlib));
1130 assert!(matches!(classify_rust_import("alloc::vec::Vec"), ImportType::Stdlib));
1131
1132 assert!(matches!(classify_rust_import("crate::models::Language"), ImportType::Internal));
1134 assert!(matches!(classify_rust_import("super::utils"), ImportType::Internal));
1135 assert!(matches!(classify_rust_import("self::helper"), ImportType::Internal));
1136
1137 assert!(matches!(classify_rust_import("serde::Serialize"), ImportType::External));
1139 assert!(matches!(classify_rust_import("anyhow::Result"), ImportType::External));
1140 assert!(matches!(classify_rust_import("tokio::runtime"), ImportType::External));
1141 }
1142}
1143
1144fn find_crate_root(start_path: &str) -> Option<String> {
1150 let path = std::path::Path::new(start_path);
1151 let mut current = path.parent()?;
1152
1153 loop {
1155 let cargo_toml = current.join("Cargo.toml");
1156 if cargo_toml.exists() {
1157 return Some(current.to_string_lossy().to_string());
1158 }
1159
1160 if current.ends_with("src") {
1163 if let Some(parent) = current.parent() {
1164 return Some(parent.to_string_lossy().to_string());
1165 }
1166 }
1167
1168 current = match current.parent() {
1170 Some(p) if p.as_os_str().is_empty() => return None,
1171 Some(p) => p,
1172 None => return None,
1173 };
1174 }
1175}
1176
1177pub fn resolve_rust_use_to_path(
1189 import_path: &str,
1190 current_file_path: Option<&str>,
1191 _project_root: Option<&str>,
1192) -> Option<String> {
1193 if !import_path.starts_with("crate::")
1195 && !import_path.starts_with("super::")
1196 && !import_path.starts_with("self::") {
1197 if import_path.contains("::") {
1199 return None; }
1201 }
1203
1204 let current_file = current_file_path?;
1205 let current_path = std::path::Path::new(current_file);
1206
1207 let crate_root = find_crate_root(current_file)?;
1209 let crate_root_path = std::path::Path::new(&crate_root);
1210
1211 if import_path.starts_with("crate::") {
1212 let module_path = import_path.strip_prefix("crate::").unwrap();
1214 let parts: Vec<&str> = module_path.split("::").collect();
1215
1216 let src_root = crate_root_path.join("src");
1218 resolve_rust_module_path(&src_root, &parts)
1219 } else if import_path.starts_with("super::") {
1220 let module_path = import_path.strip_prefix("super::").unwrap();
1222 let parts: Vec<&str> = module_path.split("::").collect();
1223
1224 let current_dir = if current_path.file_name().unwrap() == "mod.rs" {
1226 current_path.parent()?.parent()?
1228 } else {
1229 current_path.parent()?
1231 };
1232
1233 resolve_rust_module_path(current_dir, &parts)
1234 } else if import_path.starts_with("self::") {
1235 let module_path = import_path.strip_prefix("self::").unwrap();
1237 let parts: Vec<&str> = module_path.split("::").collect();
1238
1239 let current_dir = if current_path.file_name().unwrap() == "mod.rs" {
1241 current_path.parent()?
1243 } else {
1244 current_path.parent()?
1246 };
1247
1248 resolve_rust_module_path(current_dir, &parts)
1249 } else {
1250 let current_dir = current_path.parent()?;
1253 let module_file = current_dir.join(format!("{}.rs", import_path));
1254 let module_dir = current_dir.join(import_path).join("mod.rs");
1255
1256 if module_file.exists() {
1257 Some(module_file.to_string_lossy().to_string())
1258 } else if module_dir.exists() {
1259 Some(module_dir.to_string_lossy().to_string())
1260 } else {
1261 Some(module_file.to_string_lossy().to_string())
1264 }
1265 }
1266}
1267
1268fn resolve_rust_module_path(base_dir: &std::path::Path, parts: &[&str]) -> Option<String> {
1274 if parts.is_empty() {
1275 return None;
1276 }
1277
1278 let mut current_path = base_dir.to_path_buf();
1280
1281 for (i, part) in parts.iter().enumerate() {
1282 if i == parts.len() - 1 {
1283 let file_path = current_path.join(format!("{}.rs", part));
1285 let mod_path = current_path.join(part).join("mod.rs");
1286
1287 log::trace!("Checking Rust module path: {}", file_path.display());
1288 log::trace!("Checking Rust module path: {}", mod_path.display());
1289
1290 if file_path.exists() {
1292 return Some(file_path.to_string_lossy().to_string());
1293 } else if mod_path.exists() {
1294 return Some(mod_path.to_string_lossy().to_string());
1295 } else {
1296 return Some(file_path.to_string_lossy().to_string());
1298 }
1299 } else {
1300 current_path = current_path.join(part);
1302 }
1303 }
1304
1305 None
1306}
1307
1308#[cfg(test)]
1309mod path_resolution_tests {
1310 use super::*;
1311
1312 #[test]
1313 fn test_resolve_crate_import() {
1314 let result = resolve_rust_use_to_path(
1316 "crate::models",
1317 Some("/home/user/project/src/main.rs"),
1318 Some("/home/user/project"),
1319 );
1320
1321 assert!(result.is_some());
1322 let path = result.unwrap();
1323 assert!(path.contains("models.rs") || path.contains("models/mod.rs"));
1325 }
1326
1327 #[test]
1328 fn test_resolve_super_import() {
1329 let result = resolve_rust_use_to_path(
1331 "super::utils",
1332 Some("/home/user/project/src/commands/index.rs"),
1333 Some("/home/user/project"),
1334 );
1335
1336 assert!(result.is_some());
1337 let path = result.unwrap();
1338 assert!(path.contains("src") && path.contains("utils.rs"));
1340 }
1341
1342 #[test]
1343 fn test_resolve_self_import() {
1344 let result = resolve_rust_use_to_path(
1346 "self::helper",
1347 Some("/home/user/project/src/models/mod.rs"),
1348 Some("/home/user/project"),
1349 );
1350
1351 assert!(result.is_some());
1352 let path = result.unwrap();
1353 assert!(path.contains("models") && path.contains("helper.rs"));
1355 }
1356
1357 #[test]
1358 fn test_resolve_mod_declaration() {
1359 let result = resolve_rust_use_to_path(
1361 "parser",
1362 Some("/home/user/project/src/main.rs"),
1363 Some("/home/user/project"),
1364 );
1365
1366 assert!(result.is_some());
1367 let path = result.unwrap();
1368 assert!(path.contains("parser.rs"));
1370 }
1371
1372 #[test]
1373 fn test_resolve_nested_crate_import() {
1374 let result = resolve_rust_use_to_path(
1376 "crate::models::language",
1377 Some("/home/user/project/src/main.rs"),
1378 Some("/home/user/project"),
1379 );
1380
1381 assert!(result.is_some());
1382 let path = result.unwrap();
1383 assert!(path.contains("models") && (path.contains("language.rs") || path.contains("language/mod.rs")));
1385 }
1386
1387 #[test]
1388 fn test_external_import_not_supported() {
1389 let result = resolve_rust_use_to_path(
1391 "anyhow::Result",
1392 Some("/home/user/project/src/main.rs"),
1393 Some("/home/user/project"),
1394 );
1395
1396 assert!(result.is_none());
1398 }
1399
1400 #[test]
1401 fn test_stdlib_import_not_supported() {
1402 let result = resolve_rust_use_to_path(
1404 "std::collections::HashMap",
1405 Some("/home/user/project/src/main.rs"),
1406 Some("/home/user/project"),
1407 );
1408
1409 assert!(result.is_none());
1411 }
1412
1413 #[test]
1414 fn test_resolve_without_current_file() {
1415 let result = resolve_rust_use_to_path(
1416 "crate::models",
1417 None,
1418 Some("/home/user/project"),
1419 );
1420
1421 assert!(result.is_none());
1423 }
1424}