1use crate::error::{ParseError, Result};
11use crate::fallback_parser;
12use crate::node::{CodeNode, NodeKind};
13use std::collections::HashMap;
14use std::fs;
15use std::path::Path;
16use tree_sitter::{Language, Parser, Query, QueryCursor, Tree};
17
18#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct SymbolRelation {
25 pub from_id: String,
27 pub to_name: String,
29 pub kind: RelationType,
31 pub line: u32,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37pub enum RelationType {
38 Calls,
40 Imports,
42 Extends,
44 Implements,
46}
47
48#[derive(Debug)]
50pub struct ParseResult {
51 pub symbols: Vec<CodeNode>,
53 pub relations: Vec<SymbolRelation>,
55 pub file_path: String,
57}
58
59pub struct ArborParser {
68 parser: Parser,
70 queries: HashMap<String, CompiledQueries>,
72}
73
74struct CompiledQueries {
76 symbols: Query,
78 imports: Query,
80 calls: Query,
82 language: Language,
84}
85
86impl Default for ArborParser {
87 fn default() -> Self {
88 Self::new().expect("Failed to initialize ArborParser")
89 }
90}
91
92impl ArborParser {
93 pub fn new() -> Result<Self> {
97 let parser = Parser::new();
98 let mut queries = HashMap::new();
99
100 for ext in &["ts", "tsx", "js", "jsx"] {
102 let compiled = Self::compile_typescript_queries()?;
103 queries.insert(ext.to_string(), compiled);
104 }
105
106 let rs_queries = Self::compile_rust_queries()?;
108 queries.insert("rs".to_string(), rs_queries);
109
110 let py_queries = Self::compile_python_queries()?;
112 queries.insert("py".to_string(), py_queries);
113
114 let go_queries = Self::compile_go_queries()?;
116 queries.insert("go".to_string(), go_queries);
117
118 let java_queries = Self::compile_java_queries()?;
120 queries.insert("java".to_string(), java_queries);
121
122 for ext in &["c", "h"] {
124 queries.insert(ext.to_string(), Self::compile_c_queries()?);
125 }
126
127 for ext in &["cpp", "hpp", "cc", "hh", "cxx", "hxx"] {
129 queries.insert(ext.to_string(), Self::compile_cpp_queries()?);
130 }
131
132 let csharp_queries = Self::compile_csharp_queries()?;
135 queries.insert("cs".to_string(), csharp_queries);
136
137 Ok(Self { parser, queries })
138 }
139
140 pub fn parse_file(&mut self, path: &Path) -> Result<ParseResult> {
152 let source = fs::read_to_string(path).map_err(|e| ParseError::io(path, e))?;
154
155 if source.is_empty() {
156 return Err(ParseError::EmptyFile(path.to_path_buf()));
157 }
158
159 let ext = path
161 .extension()
162 .and_then(|e| e.to_str())
163 .ok_or_else(|| ParseError::UnsupportedLanguage(path.to_path_buf()))?;
164 let ext = ext.to_ascii_lowercase();
165
166 let compiled = match self.queries.get(&ext) {
168 Some(compiled) => compiled,
169 None => {
170 if fallback_parser::is_fallback_supported_extension(&ext) {
171 return Ok(ParseResult {
172 symbols: fallback_parser::parse_fallback_source(
173 &source,
174 &path.to_string_lossy(),
175 &ext,
176 ),
177 relations: Vec::new(),
178 file_path: path.to_string_lossy().to_string(),
179 });
180 }
181 return Err(ParseError::UnsupportedLanguage(path.to_path_buf()));
182 }
183 };
184
185 self.parser
187 .set_language(&compiled.language)
188 .map_err(|e| ParseError::ParserError(format!("Failed to set language: {}", e)))?;
189
190 let tree = self
192 .parser
193 .parse(&source, None)
194 .ok_or_else(|| ParseError::ParserError("Tree-sitter returned no tree".into()))?;
195
196 let file_path = path.to_string_lossy().to_string();
197 let file_name = path
198 .file_name()
199 .and_then(|n| n.to_str())
200 .unwrap_or("unknown");
201
202 let symbols = self.extract_symbols(&tree, &source, &file_path, file_name, compiled);
204
205 let relations = self.extract_relations(&tree, &source, &file_path, &symbols, compiled);
207
208 Ok(ParseResult {
209 symbols,
210 relations,
211 file_path,
212 })
213 }
214
215 pub fn parse_source(
217 &mut self,
218 source: &str,
219 file_path: &str,
220 language: &str,
221 ) -> Result<ParseResult> {
222 if source.is_empty() {
223 return Err(ParseError::EmptyFile(file_path.into()));
224 }
225
226 let language = language.to_ascii_lowercase();
228 let compiled = self.queries.get(&language);
229
230 if compiled.is_none() {
231 if fallback_parser::is_fallback_supported_extension(&language) {
232 return Ok(ParseResult {
233 symbols: fallback_parser::parse_fallback_source(source, file_path, &language),
234 relations: Vec::new(),
235 file_path: file_path.to_string(),
236 });
237 }
238 return Err(ParseError::UnsupportedLanguage(file_path.into()));
239 }
240
241 let compiled = compiled.unwrap();
242
243 self.parser
244 .set_language(&compiled.language)
245 .map_err(|e| ParseError::ParserError(format!("Failed to set language: {}", e)))?;
246
247 let tree = self
248 .parser
249 .parse(source, None)
250 .ok_or_else(|| ParseError::ParserError("Tree-sitter returned no tree".into()))?;
251
252 let file_name = Path::new(file_path)
253 .file_name()
254 .and_then(|n| n.to_str())
255 .unwrap_or("unknown");
256
257 let symbols = self.extract_symbols(&tree, source, file_path, file_name, compiled);
258 let relations = self.extract_relations(&tree, source, file_path, &symbols, compiled);
259
260 Ok(ParseResult {
261 symbols,
262 relations,
263 file_path: file_path.to_string(),
264 })
265 }
266
267 fn extract_symbols(
272 &self,
273 tree: &Tree,
274 source: &str,
275 file_path: &str,
276 file_name: &str,
277 compiled: &CompiledQueries,
278 ) -> Vec<CodeNode> {
279 let mut symbols = Vec::new();
280 let mut cursor = QueryCursor::new();
281
282 let matches = cursor.matches(&compiled.symbols, tree.root_node(), source.as_bytes());
283
284 for match_ in matches {
285 let mut name: Option<&str> = None;
287 let mut kind: Option<NodeKind> = None;
288 let mut node = match_.captures.first().map(|c| c.node);
289
290 for capture in match_.captures {
291 let capture_name = compiled.symbols.capture_names()[capture.index as usize];
292 let text = capture.node.utf8_text(source.as_bytes()).unwrap_or("");
293
294 match capture_name {
295 "name" | "function.name" | "class.name" | "interface.name" | "method.name" => {
296 name = Some(text);
297 }
298 "function" | "function_def" => {
299 kind = Some(NodeKind::Function);
300 node = Some(capture.node);
301 }
302 "class" | "class_def" => {
303 kind = Some(NodeKind::Class);
304 node = Some(capture.node);
305 }
306 "interface" | "interface_def" => {
307 kind = Some(NodeKind::Interface);
308 node = Some(capture.node);
309 }
310 "method" | "method_def" => {
311 kind = Some(NodeKind::Method);
312 node = Some(capture.node);
313 }
314 "struct" | "struct_def" => {
315 kind = Some(NodeKind::Struct);
316 node = Some(capture.node);
317 }
318 "enum" | "enum_def" => {
319 kind = Some(NodeKind::Enum);
320 node = Some(capture.node);
321 }
322 "trait" | "trait_def" => {
323 kind = Some(NodeKind::Interface);
324 node = Some(capture.node);
325 }
326 _ => {}
327 }
328 }
329
330 if let (Some(name), Some(kind), Some(node)) = (name, kind, node) {
331 let qualified_name = format!("{}:{}", file_name, name);
333
334 let signature = source
336 .lines()
337 .nth(node.start_position().row)
338 .map(|s| s.trim().to_string());
339
340 let mut symbol = CodeNode::new(name, &qualified_name, kind, file_path)
341 .with_lines(
342 node.start_position().row as u32 + 1,
343 node.end_position().row as u32 + 1,
344 )
345 .with_column(node.start_position().column as u32)
346 .with_bytes(node.start_byte() as u32, node.end_byte() as u32);
347
348 if let Some(sig) = signature {
349 symbol = symbol.with_signature(sig);
350 }
351
352 symbols.push(symbol);
353 }
354 }
355
356 symbols
357 }
358
359 fn extract_relations(
364 &self,
365 tree: &Tree,
366 source: &str,
367 file_path: &str,
368 symbols: &[CodeNode],
369 compiled: &CompiledQueries,
370 ) -> Vec<SymbolRelation> {
371 let mut relations = Vec::new();
372
373 self.extract_imports(tree, source, file_path, &mut relations, compiled);
375
376 self.extract_calls(tree, source, file_path, symbols, &mut relations, compiled);
378
379 relations
380 }
381
382 fn extract_imports(
383 &self,
384 tree: &Tree,
385 source: &str,
386 file_path: &str,
387 relations: &mut Vec<SymbolRelation>,
388 compiled: &CompiledQueries,
389 ) {
390 let mut cursor = QueryCursor::new();
391 let matches = cursor.matches(&compiled.imports, tree.root_node(), source.as_bytes());
392
393 for match_ in matches {
394 let mut module_name: Option<&str> = None;
395 let mut line: u32 = 0;
396
397 for capture in match_.captures {
398 let capture_name = compiled.imports.capture_names()[capture.index as usize];
399 let text = capture.node.utf8_text(source.as_bytes()).unwrap_or("");
400
401 match capture_name {
402 "source" | "module" | "import.source" => {
403 module_name = Some(text.trim_matches(|c| c == '"' || c == '\''));
405 line = capture.node.start_position().row as u32 + 1;
406 }
407 _ => {}
408 }
409 }
410
411 if let Some(module) = module_name {
412 let file_id = format!("{}:__file__", file_path);
414 relations.push(SymbolRelation {
415 from_id: file_id,
416 to_name: module.to_string(),
417 kind: RelationType::Imports,
418 line,
419 });
420 }
421 }
422 }
423
424 fn extract_calls(
425 &self,
426 tree: &Tree,
427 source: &str,
428 file_path: &str,
429 symbols: &[CodeNode],
430 relations: &mut Vec<SymbolRelation>,
431 compiled: &CompiledQueries,
432 ) {
433 let mut cursor = QueryCursor::new();
434 let matches = cursor.matches(&compiled.calls, tree.root_node(), source.as_bytes());
435
436 for match_ in matches {
437 let mut callee_name: Option<&str> = None;
438 let mut call_line: u32 = 0;
439
440 for capture in match_.captures {
441 let capture_name = compiled.calls.capture_names()[capture.index as usize];
442 let text = capture.node.utf8_text(source.as_bytes()).unwrap_or("");
443
444 match capture_name {
445 "callee" | "function" | "call.function" => {
446 if let Some(dot_pos) = text.rfind('.') {
448 callee_name = Some(&text[dot_pos + 1..]);
449 } else {
450 callee_name = Some(text);
451 }
452 call_line = capture.node.start_position().row as u32 + 1;
453 }
454 _ => {}
455 }
456 }
457
458 if let Some(callee) = callee_name {
459 let caller_id = self
461 .find_enclosing_symbol(call_line, symbols)
462 .map(|s| s.id.clone())
463 .unwrap_or_else(|| format!("{}:__file__", file_path));
464
465 relations.push(SymbolRelation {
466 from_id: caller_id,
467 to_name: callee.to_string(),
468 kind: RelationType::Calls,
469 line: call_line,
470 });
471 }
472 }
473 }
474
475 fn find_enclosing_symbol<'a>(
476 &self,
477 line: u32,
478 symbols: &'a [CodeNode],
479 ) -> Option<&'a CodeNode> {
480 symbols
481 .iter()
482 .filter(|s| s.line_start <= line && s.line_end >= line)
483 .min_by_key(|s| s.line_end - s.line_start) }
485
486 fn compile_queries(
494 language: Language,
495 symbols_query: &str,
496 imports_query: &str,
497 calls_query: &str,
498 ) -> Result<CompiledQueries> {
499 let symbols = Query::new(&language, symbols_query)
500 .map_err(|e| ParseError::QueryError(e.to_string()))?;
501 let imports = Query::new(&language, imports_query)
502 .map_err(|e| ParseError::QueryError(e.to_string()))?;
503 let calls = Query::new(&language, calls_query)
504 .map_err(|e| ParseError::QueryError(e.to_string()))?;
505
506 Ok(CompiledQueries {
507 symbols,
508 imports,
509 calls,
510 language,
511 })
512 }
513
514 fn compile_typescript_queries() -> Result<CompiledQueries> {
515 let language = tree_sitter_typescript::language_typescript();
516
517 let symbols_query = r#"
518 (function_declaration name: (identifier) @name) @function_def
519 (class_declaration name: (type_identifier) @name) @class_def
520 (method_definition name: (property_identifier) @name) @method_def
521 (interface_declaration name: (type_identifier) @name) @interface_def
522 (type_alias_declaration name: (type_identifier) @name) @interface_def
523 "#;
524
525 let imports_query = r#"
526 (import_statement
527 source: (string) @source)
528 "#;
529
530 let calls_query = r#"
531 (call_expression
532 function: (identifier) @callee)
533
534 (call_expression
535 function: (member_expression
536 property: (property_identifier) @callee))
537 "#;
538
539 Self::compile_queries(language, symbols_query, imports_query, calls_query)
540 }
541
542 fn compile_rust_queries() -> Result<CompiledQueries> {
543 let language = tree_sitter_rust::language();
544
545 let symbols_query = r#"
546 (function_item name: (identifier) @name) @function_def
547 (struct_item name: (type_identifier) @name) @struct_def
548 (enum_item name: (type_identifier) @name) @enum_def
549 (trait_item name: (type_identifier) @name) @trait_def
550 "#;
551
552 let imports_query = r#"
553 (use_declaration) @source
554 "#;
555
556 let calls_query = r#"
557 (call_expression function: (identifier) @callee)
558 (call_expression function: (field_expression field: (field_identifier) @callee))
559 "#;
560
561 Self::compile_queries(language, symbols_query, imports_query, calls_query)
562 }
563
564 fn compile_python_queries() -> Result<CompiledQueries> {
565 let language = tree_sitter_python::language();
566
567 let symbols_query = r#"
568 (function_definition name: (identifier) @name) @function_def
569 (class_definition name: (identifier) @name) @class_def
570 "#;
571
572 let imports_query = r#"
573 (import_statement) @source
574 (import_from_statement) @source
575 "#;
576
577 let calls_query = r#"
578 (call function: (identifier) @callee)
579 (call function: (attribute attribute: (identifier) @callee))
580 "#;
581
582 Self::compile_queries(language, symbols_query, imports_query, calls_query)
583 }
584
585 fn compile_go_queries() -> Result<CompiledQueries> {
586 let language = tree_sitter_go::language();
587
588 let symbols_query = r#"
589 (function_declaration name: (identifier) @name) @function_def
590 (method_declaration name: (field_identifier) @name) @method_def
591 (type_declaration (type_spec name: (type_identifier) @name type: (struct_type))) @struct_def
592 (type_declaration (type_spec name: (type_identifier) @name type: (interface_type))) @interface_def
593 "#;
594
595 let imports_query = r#"
596 (import_spec path: (interpreted_string_literal) @source)
597 "#;
598
599 let calls_query = r#"
600 (call_expression function: (identifier) @callee)
601 (call_expression function: (selector_expression field: (field_identifier) @callee))
602 "#;
603
604 Self::compile_queries(language, symbols_query, imports_query, calls_query)
605 }
606
607 fn compile_java_queries() -> Result<CompiledQueries> {
608 let language = tree_sitter_java::language();
609
610 let symbols_query = r#"
611 (method_declaration name: (identifier) @name) @method_def
612 (class_declaration name: (identifier) @name) @class_def
613 (interface_declaration name: (identifier) @name) @interface_def
614 (constructor_declaration name: (identifier) @name) @function_def
615 "#;
616
617 let imports_query = r#"
618 (import_declaration) @source
619 "#;
620
621 let calls_query = r#"
622 (method_invocation name: (identifier) @callee)
623 "#;
624
625 Self::compile_queries(language, symbols_query, imports_query, calls_query)
626 }
627
628 fn compile_c_queries() -> Result<CompiledQueries> {
629 let language = tree_sitter_c::language();
630
631 let symbols_query = r#"
632 (function_definition declarator: (function_declarator declarator: (identifier) @name)) @function_def
633 (struct_specifier name: (type_identifier) @name) @struct_def
634 (enum_specifier name: (type_identifier) @name) @enum_def
635 "#;
636
637 let imports_query = r#"
638 (preproc_include path: (string_literal) @source)
639 (preproc_include path: (system_lib_string) @source)
640 "#;
641
642 let calls_query = r#"
643 (call_expression function: (identifier) @callee)
644 "#;
645
646 Self::compile_queries(language, symbols_query, imports_query, calls_query)
647 }
648
649 fn compile_cpp_queries() -> Result<CompiledQueries> {
650 let language = tree_sitter_cpp::language();
651
652 let symbols_query = r#"
653 (function_definition declarator: (function_declarator declarator: (identifier) @name)) @function_def
654 (function_definition declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name))) @method_def
655 (class_specifier name: (type_identifier) @name) @class_def
656 (struct_specifier name: (type_identifier) @name) @struct_def
657 "#;
658
659 let imports_query = r#"
660 (preproc_include path: (string_literal) @source)
661 (preproc_include path: (system_lib_string) @source)
662 "#;
663
664 let calls_query = r#"
665 (call_expression function: (identifier) @callee)
666 (call_expression function: (field_expression field: (field_identifier) @callee))
667 "#;
668
669 Self::compile_queries(language, symbols_query, imports_query, calls_query)
670 }
671
672 fn compile_csharp_queries() -> Result<CompiledQueries> {
673 let language = tree_sitter_c_sharp::language();
674
675 let symbols_query = r#"
676 (method_declaration name: (identifier) @name) @method_def
677 (class_declaration name: (identifier) @name) @class_def
678 (interface_declaration name: (identifier) @name) @interface_def
679 (struct_declaration name: (identifier) @name) @struct_def
680 (constructor_declaration name: (identifier) @name) @function_def
681 (property_declaration name: (identifier) @name) @method_def
682 "#;
683
684 let imports_query = r#"
685 (using_directive (identifier) @source)
686 (using_directive (qualified_name) @source)
687 "#;
688
689 let calls_query = r#"
690 (invocation_expression function: (identifier) @callee)
691 (invocation_expression function: (member_access_expression name: (identifier) @callee))
692 "#;
693
694 Self::compile_queries(language, symbols_query, imports_query, calls_query)
695 }
696}
697
698#[cfg(test)]
703mod tests {
704 use super::*;
705
706 #[test]
707 fn test_parser_initialization() {
708 match ArborParser::new() {
710 Ok(_) => println!("Parser initialized successfully!"),
711 Err(e) => panic!("Parser failed to initialize: {}", e),
712 }
713 }
714
715 #[test]
716 fn test_parse_typescript_symbols() {
717 let mut parser = ArborParser::new().unwrap();
718
719 let source = r#"
720 function greet(name: string): string {
721 return `Hello, ${name}!`;
722 }
723
724 export class UserService {
725 validate(user: User): boolean {
726 return true;
727 }
728 }
729
730 interface User {
731 name: string;
732 email: string;
733 }
734 "#;
735
736 let result = parser.parse_source(source, "test.ts", "ts").unwrap();
737
738 assert!(result.symbols.iter().any(|s| s.name == "greet"));
739 assert!(result.symbols.iter().any(|s| s.name == "UserService"));
740 assert!(result.symbols.iter().any(|s| s.name == "validate"));
741 assert!(result.symbols.iter().any(|s| s.name == "User"));
742 }
743
744 #[test]
745 fn test_parse_typescript_imports() {
746 let mut parser = ArborParser::new().unwrap();
747
748 let source = r#"
749 import { useState } from 'react';
750 import lodash from 'lodash';
751
752 function Component() {
753 const [count, setCount] = useState(0);
754 }
755 "#;
756
757 let result = parser.parse_source(source, "test.ts", "ts").unwrap();
758
759 let imports: Vec<_> = result
760 .relations
761 .iter()
762 .filter(|r| r.kind == RelationType::Imports)
763 .collect();
764
765 assert!(imports.iter().any(|i| i.to_name.contains("react")));
766 assert!(imports.iter().any(|i| i.to_name.contains("lodash")));
767 }
768
769 #[test]
770 fn test_parse_typescript_calls() {
771 let mut parser = ArborParser::new().unwrap();
772
773 let source = r#"
774 function outer() {
775 inner();
776 helper.process();
777 }
778
779 function inner() {
780 console.log("Hello");
781 }
782 "#;
783
784 let result = parser.parse_source(source, "test.ts", "ts").unwrap();
785
786 let calls: Vec<_> = result
787 .relations
788 .iter()
789 .filter(|r| r.kind == RelationType::Calls)
790 .collect();
791
792 assert!(calls.iter().any(|c| c.to_name == "inner"));
793 assert!(calls.iter().any(|c| c.to_name == "process"));
794 assert!(calls.iter().any(|c| c.to_name == "log"));
795 }
796
797 #[test]
798 fn test_parse_rust_symbols() {
799 let mut parser = ArborParser::new().unwrap();
800
801 let source = r#"
802 fn main() {
803 println!("Hello!");
804 }
805
806 pub struct User {
807 name: String,
808 }
809
810 impl User {
811 fn new(name: &str) -> Self {
812 Self { name: name.to_string() }
813 }
814 }
815
816 enum Status {
817 Active,
818 Inactive,
819 }
820 "#;
821
822 let result = parser.parse_source(source, "test.rs", "rs").unwrap();
823
824 assert!(result.symbols.iter().any(|s| s.name == "main"));
825 assert!(result.symbols.iter().any(|s| s.name == "User"));
826 assert!(result.symbols.iter().any(|s| s.name == "new"));
827 assert!(result.symbols.iter().any(|s| s.name == "Status"));
828 }
829
830 #[test]
831 fn test_parse_python_symbols() {
832 let mut parser = ArborParser::new().unwrap();
833
834 let source = r#"
835def greet(name):
836 return f"Hello, {name}!"
837
838class UserService:
839 def validate(self, user):
840 return True
841 "#;
842
843 let result = parser.parse_source(source, "test.py", "py").unwrap();
844
845 assert!(result.symbols.iter().any(|s| s.name == "greet"));
846 assert!(result.symbols.iter().any(|s| s.name == "UserService"));
847 assert!(result.symbols.iter().any(|s| s.name == "validate"));
848 }
849
850 #[test]
851 fn test_parse_fallback_kotlin_symbols() {
852 let mut parser = ArborParser::new().unwrap();
853
854 let source = r#"
855 class BillingService
856 fun computeInvoiceTotal(amount: Double): Double = amount
857 "#;
858
859 let result = parser.parse_source(source, "billing.kt", "kt").unwrap();
860
861 assert!(result.symbols.iter().any(|s| s.name == "BillingService"));
862 assert!(result
863 .symbols
864 .iter()
865 .any(|s| s.name == "computeInvoiceTotal"));
866 assert!(result.relations.is_empty());
867 }
868
869 #[test]
870 fn test_parse_go_symbols() {
871 let mut parser = ArborParser::new().unwrap();
872
873 let source = r#"
874package main
875
876import "fmt"
877
878func greet(name string) string {
879 return fmt.Sprintf("Hello, %s!", name)
880}
881
882type User struct {
883 Name string
884 Age int
885}
886
887type Service interface {
888 Process(data []byte) error
889}
890"#;
891
892 let result = parser.parse_source(source, "main.go", "go").unwrap();
893
894 assert!(result.symbols.iter().any(|s| s.name == "greet"));
895 assert!(result.symbols.iter().any(|s| s.name == "User"));
896 assert!(result.symbols.iter().any(|s| s.name == "Service"));
897 }
898
899 #[test]
900 fn test_parse_java_symbols() {
901 let mut parser = ArborParser::new().unwrap();
902
903 let source = r#"
904package com.example;
905
906import java.util.List;
907
908public class OrderService {
909 public void processOrder(String orderId) {
910 validate(orderId);
911 }
912
913 private void validate(String id) {
914 }
915}
916"#;
917
918 let result = parser
919 .parse_source(source, "OrderService.java", "java")
920 .unwrap();
921
922 assert!(result.symbols.iter().any(|s| s.name == "OrderService"));
923 assert!(result.symbols.iter().any(|s| s.name == "processOrder"));
924 assert!(result.symbols.iter().any(|s| s.name == "validate"));
925 }
926
927 #[test]
928 fn test_parse_c_symbols() {
929 let mut parser = ArborParser::new().unwrap();
930
931 let source = r#"
932#include <stdio.h>
933
934struct Point {
935 int x;
936 int y;
937};
938
939void print_point(struct Point p) {
940 printf("(%d, %d)\n", p.x, p.y);
941}
942
943int add(int a, int b) {
944 return a + b;
945}
946"#;
947
948 let result = parser.parse_source(source, "math.c", "c").unwrap();
949
950 assert!(result.symbols.iter().any(|s| s.name == "Point"));
951 assert!(result.symbols.iter().any(|s| s.name == "print_point"));
952 assert!(result.symbols.iter().any(|s| s.name == "add"));
953 }
954
955 #[test]
956 fn test_parse_cpp_symbols() {
957 let mut parser = ArborParser::new().unwrap();
958
959 let source = r#"
960#include <iostream>
961
962class Calculator {
963public:
964 int add(int a, int b) {
965 return a + b;
966 }
967};
968
969struct Config {
970 int timeout;
971};
972
973void helpers() {
974 std::cout << "ok" << std::endl;
975}
976"#;
977
978 let result = parser.parse_source(source, "calc.cpp", "cpp").unwrap();
979
980 assert!(result.symbols.iter().any(|s| s.name == "Calculator"));
981 assert!(result.symbols.iter().any(|s| s.name == "Config"));
982 assert!(result.symbols.iter().any(|s| s.name == "helpers"));
983 }
984
985 #[test]
986 fn test_parse_csharp_symbols() {
987 let mut parser = ArborParser::new().unwrap();
988
989 let source = r#"
990using System;
991
992namespace MyApp
993{
994 public class UserController
995 {
996 public string GetUser(int id)
997 {
998 return "user";
999 }
1000 }
1001
1002 public interface IRepository
1003 {
1004 void Save(string data);
1005 }
1006}
1007"#;
1008
1009 let result = parser
1010 .parse_source(source, "UserController.cs", "cs")
1011 .unwrap();
1012
1013 assert!(result.symbols.iter().any(|s| s.name == "UserController"));
1014 assert!(result.symbols.iter().any(|s| s.name == "GetUser"));
1015 assert!(result.symbols.iter().any(|s| s.name == "IRepository"));
1016 assert!(result.symbols.iter().any(|s| s.name == "Save"));
1017 }
1018
1019 #[test]
1020 fn test_parse_unsupported_extension_errors() {
1021 let mut parser = ArborParser::new().unwrap();
1022 let result = parser.parse_source("anything", "test.xyz", "xyz");
1023 assert!(result.is_err());
1024 }
1025
1026 #[test]
1027 fn test_parse_result_file_path() {
1028 let mut parser = ArborParser::new().unwrap();
1029 let result = parser
1030 .parse_source("fn main() {}", "test.rs", "rs")
1031 .unwrap();
1032 assert_eq!(result.file_path, "test.rs");
1033 }
1034
1035 #[test]
1036 fn test_parse_python_imports_detected() {
1037 let mut parser = ArborParser::new().unwrap();
1038
1039 let source = r#"
1040import os
1041from pathlib import Path
1042
1043def read_file(path):
1044 with open(path) as f:
1045 return f.read()
1046"#;
1047
1048 let result = parser.parse_source(source, "utils.py", "py").unwrap();
1049 assert!(result.symbols.iter().any(|s| s.name == "read_file"));
1050 assert!(result
1051 .relations
1052 .iter()
1053 .any(|r| r.kind == RelationType::Imports));
1054 }
1055}