Skip to main content

arbor_core/
parser_v2.rs

1//! ArborParser - The Eyes of Arbor
2//!
3//! This module implements high-performance code parsing using Tree-sitter queries.
4//! It extracts symbols (functions, classes, interfaces) and their relationships
5//! (imports, calls) to build a comprehensive code graph.
6//!
7//! The parser is designed for incremental updates - calling it on the same file
8//! will update existing nodes rather than creating duplicates.
9
10use 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 tracing::warn;
17use tree_sitter::{Language, Node, Parser, Query, QueryCursor, Tree};
18
19// ─────────────────────────────────────────────────────────────────────────────
20// Types
21// ─────────────────────────────────────────────────────────────────────────────
22
23/// A relationship between two symbols in the code.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct SymbolRelation {
26    /// The source symbol (caller/importer).
27    pub from_id: String,
28    /// The target symbol name (what is being called/imported).
29    pub to_name: String,
30    /// The type of relationship.
31    pub kind: RelationType,
32    /// Line number where the relationship occurs.
33    pub line: u32,
34}
35
36/// Types of relationships between code symbols.
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38pub enum RelationType {
39    /// Function/method calls another function.
40    Calls,
41    /// Module imports another module or symbol.
42    Imports,
43    /// Class extends another class.
44    Extends,
45    /// Class/type implements an interface.
46    Implements,
47}
48
49/// Result of parsing a single file.
50#[derive(Debug)]
51pub struct ParseResult {
52    /// Extracted code symbols.
53    pub symbols: Vec<CodeNode>,
54    /// Relationships between symbols.
55    pub relations: Vec<SymbolRelation>,
56    /// File path that was parsed.
57    pub file_path: String,
58}
59
60// ─────────────────────────────────────────────────────────────────────────────
61// ArborParser
62// ─────────────────────────────────────────────────────────────────────────────
63
64/// High-performance code parser using Tree-sitter queries.
65///
66/// The parser caches compiled queries for reuse across multiple files,
67/// making it efficient for large codebase indexing.
68pub struct ArborParser {
69    /// Tree-sitter parser instance.
70    parser: Parser,
71    /// Compiled queries by language.
72    queries: HashMap<String, CompiledQueries>,
73}
74
75/// Pre-compiled queries for a specific language.
76struct CompiledQueries {
77    /// Query for extracting symbols (functions, classes, etc.).
78    symbols: Query,
79    /// Query for extracting imports.
80    imports: Query,
81    /// Query for extracting function calls.
82    calls: Query,
83    /// The language for this query set.
84    language: Language,
85}
86
87impl Default for ArborParser {
88    fn default() -> Self {
89        match Self::new() {
90            Ok(parser) => parser,
91            Err(error) => {
92                warn!(
93                    "ArborParser::new failed during default init; continuing with empty query registry: {}",
94                    error
95                );
96                Self {
97                    parser: Parser::new(),
98                    queries: HashMap::new(),
99                }
100            }
101        }
102    }
103}
104
105impl ArborParser {
106    /// Creates a new ArborParser with pre-compiled queries.
107    ///
108    /// Returns an error if any language queries fail to compile.
109    pub fn new() -> Result<Self> {
110        let parser = Parser::new();
111        let mut queries = HashMap::new();
112
113        // Compile TypeScript/JavaScript queries
114        for ext in &["ts", "tsx", "js", "jsx"] {
115            let compiled = Self::compile_typescript_queries()?;
116            queries.insert(ext.to_string(), compiled);
117        }
118
119        // Compile Rust queries
120        let rs_queries = Self::compile_rust_queries()?;
121        queries.insert("rs".to_string(), rs_queries);
122
123        // Compile Python queries
124        let py_queries = Self::compile_python_queries()?;
125        queries.insert("py".to_string(), py_queries);
126
127        // Compile Go queries
128        let go_queries = Self::compile_go_queries()?;
129        queries.insert("go".to_string(), go_queries);
130
131        // Compile Java queries
132        let java_queries = Self::compile_java_queries()?;
133        queries.insert("java".to_string(), java_queries);
134
135        // Compile C queries
136        for ext in &["c", "h"] {
137            queries.insert(ext.to_string(), Self::compile_c_queries()?);
138        }
139
140        // Compile C++ queries
141        for ext in &["cpp", "hpp", "cc", "hh", "cxx", "hxx"] {
142            queries.insert(ext.to_string(), Self::compile_cpp_queries()?);
143        }
144
145        // Dart remains on legacy path (queries incompatible with tree-sitter-dart v0.0.4). Registry helper enables trivial future addition.
146        // Compile C# queries
147        let csharp_queries = Self::compile_csharp_queries()?;
148        queries.insert("cs".to_string(), csharp_queries);
149
150        Ok(Self { parser, queries })
151    }
152
153    /// Parses a file and extracts symbols and relationships.
154    ///
155    /// This is the main entry point for parsing. It returns a ParseResult
156    /// containing all symbols and their relationships, ready to be inserted
157    /// into an ArborGraph.
158    ///
159    /// # Errors
160    ///
161    /// Returns an error if the file cannot be read, the language is unsupported,
162    /// or parsing fails. Syntax errors in the source code are handled gracefully -
163    /// the parser will still extract what it can.
164    pub fn parse_file(&mut self, path: &Path) -> Result<ParseResult> {
165        // Read the file
166        let source = fs::read_to_string(path).map_err(|e| ParseError::io(path, e))?;
167
168        if source.is_empty() {
169            return Err(ParseError::EmptyFile(path.to_path_buf()));
170        }
171
172        // Get the extension (normalize to lowercase)
173        let ext = path
174            .extension()
175            .and_then(|e| e.to_str())
176            .ok_or_else(|| ParseError::UnsupportedLanguage(path.to_path_buf()))?;
177        let ext = ext.to_ascii_lowercase();
178
179        // Get compiled queries
180        let compiled = match self.queries.get(&ext) {
181            Some(compiled) => compiled,
182            None => {
183                if fallback_parser::is_fallback_supported_extension(&ext) {
184                    return Ok(ParseResult {
185                        symbols: fallback_parser::parse_fallback_source(
186                            &source,
187                            &path.to_string_lossy(),
188                            &ext,
189                        ),
190                        relations: Vec::new(),
191                        file_path: path.to_string_lossy().to_string(),
192                    });
193                }
194                return Err(ParseError::UnsupportedLanguage(path.to_path_buf()));
195            }
196        };
197
198        // Configure parser for this language
199        self.parser
200            .set_language(&compiled.language)
201            .map_err(|e| ParseError::ParserError(format!("Failed to set language: {}", e)))?;
202
203        // Parse the source
204        let Some(tree) = self.parser.parse(&source, None) else {
205            warn!(
206                "Tree-sitter returned no tree for file '{}'; returning partial empty parse result",
207                path.to_string_lossy()
208            );
209            return Ok(ParseResult {
210                symbols: Vec::new(),
211                relations: Vec::new(),
212                file_path: path.to_string_lossy().to_string(),
213            });
214        };
215
216        let file_path = path.to_string_lossy().to_string();
217        let file_name = path
218            .file_name()
219            .and_then(|n| n.to_str())
220            .unwrap_or("unknown");
221
222        // Extract symbols
223        let symbols = self.extract_symbols(&tree, &source, &file_path, file_name, compiled);
224
225        // Extract relationships
226        let relations = self.extract_relations(&tree, &source, &file_path, &symbols, compiled);
227
228        Ok(ParseResult {
229            symbols,
230            relations,
231            file_path,
232        })
233    }
234
235    /// Parses source code directly (for testing or in-memory content).
236    pub fn parse_source(
237        &mut self,
238        source: &str,
239        file_path: &str,
240        language: &str,
241    ) -> Result<ParseResult> {
242        if source.is_empty() {
243            return Err(ParseError::EmptyFile(file_path.into()));
244        }
245
246        // Normalize language/extension to lowercase
247        let language = language.to_ascii_lowercase();
248        let compiled = match self.queries.get(&language) {
249            Some(compiled) => compiled,
250            None => {
251                if fallback_parser::is_fallback_supported_extension(&language) {
252                    return Ok(ParseResult {
253                        symbols: fallback_parser::parse_fallback_source(
254                            source, file_path, &language,
255                        ),
256                        relations: Vec::new(),
257                        file_path: file_path.to_string(),
258                    });
259                }
260                return Err(ParseError::UnsupportedLanguage(file_path.into()));
261            }
262        };
263
264        self.parser
265            .set_language(&compiled.language)
266            .map_err(|e| ParseError::ParserError(format!("Failed to set language: {}", e)))?;
267
268        let Some(tree) = self.parser.parse(source, None) else {
269            warn!(
270                "Tree-sitter returned no tree for in-memory source '{}'; returning partial empty parse result",
271                file_path
272            );
273            return Ok(ParseResult {
274                symbols: Vec::new(),
275                relations: Vec::new(),
276                file_path: file_path.to_string(),
277            });
278        };
279
280        let file_name = Path::new(file_path)
281            .file_name()
282            .and_then(|n| n.to_str())
283            .unwrap_or("unknown");
284
285        let symbols = self.extract_symbols(&tree, source, file_path, file_name, compiled);
286        let relations = self.extract_relations(&tree, source, file_path, &symbols, compiled);
287
288        Ok(ParseResult {
289            symbols,
290            relations,
291            file_path: file_path.to_string(),
292        })
293    }
294
295    // ─────────────────────────────────────────────────────────────────────────
296    // Symbol Extraction
297    // ─────────────────────────────────────────────────────────────────────────
298
299    fn extract_symbols(
300        &self,
301        tree: &Tree,
302        source: &str,
303        file_path: &str,
304        file_name: &str,
305        compiled: &CompiledQueries,
306    ) -> Vec<CodeNode> {
307        let mut symbols = Vec::new();
308        let mut cursor = QueryCursor::new();
309        let symbol_capture_names = compiled.symbols.capture_names();
310        let source_bytes = source.as_bytes();
311
312        let matches = cursor.matches(&compiled.symbols, tree.root_node(), source_bytes);
313
314        for match_ in matches {
315            // Extract name and type from captures
316            let mut name: Option<&str> = None;
317            let mut kind: Option<NodeKind> = None;
318            let mut node = match_.captures.first().map(|c| c.node);
319
320            for capture in match_.captures {
321                let Some(capture_name) = symbol_capture_names.get(capture.index as usize) else {
322                    warn!(
323                        "Symbol capture index out of bounds (index={} file='{}')",
324                        capture.index, file_path
325                    );
326                    continue;
327                };
328
329                let Some(text) = Self::node_text(capture.node, source_bytes, file_path) else {
330                    continue;
331                };
332
333                let capture_name = *capture_name;
334                match capture_name {
335                    "name" | "function.name" | "class.name" | "interface.name" | "method.name" => {
336                        name = Some(text);
337                    }
338                    "function" | "function_def" => {
339                        kind = Some(NodeKind::Function);
340                        node = Some(capture.node);
341                    }
342                    "class" | "class_def" => {
343                        kind = Some(NodeKind::Class);
344                        node = Some(capture.node);
345                    }
346                    "interface" | "interface_def" => {
347                        kind = Some(NodeKind::Interface);
348                        node = Some(capture.node);
349                    }
350                    "method" | "method_def" => {
351                        kind = Some(NodeKind::Method);
352                        node = Some(capture.node);
353                    }
354                    "struct" | "struct_def" => {
355                        kind = Some(NodeKind::Struct);
356                        node = Some(capture.node);
357                    }
358                    "enum" | "enum_def" => {
359                        kind = Some(NodeKind::Enum);
360                        node = Some(capture.node);
361                    }
362                    "trait" | "trait_def" => {
363                        kind = Some(NodeKind::Interface);
364                        node = Some(capture.node);
365                    }
366                    _ => {}
367                }
368            }
369
370            if let (Some(name), Some(kind), Some(node)) = (name, kind, node) {
371                // Build fully qualified name: filename:symbol_name
372                let qualified_name = format!("{}:{}", file_name, name);
373
374                // Extract signature from the node slice (avoids scanning from start each time)
375                let signature = Self::first_line_signature(source, node);
376
377                let mut symbol = CodeNode::new(name, &qualified_name, kind, file_path)
378                    .with_lines(
379                        node.start_position().row as u32 + 1,
380                        node.end_position().row as u32 + 1,
381                    )
382                    .with_column(node.start_position().column as u32)
383                    .with_bytes(node.start_byte() as u32, node.end_byte() as u32);
384
385                if let Some(sig) = signature {
386                    symbol = symbol.with_signature(sig.to_owned());
387                }
388
389                symbols.push(symbol);
390            }
391        }
392
393        symbols
394    }
395
396    // ─────────────────────────────────────────────────────────────────────────
397    // Relationship Extraction
398    // ─────────────────────────────────────────────────────────────────────────
399
400    fn extract_relations(
401        &self,
402        tree: &Tree,
403        source: &str,
404        file_path: &str,
405        symbols: &[CodeNode],
406        compiled: &CompiledQueries,
407    ) -> Vec<SymbolRelation> {
408        let mut relations = Vec::new();
409        let mut cursor = QueryCursor::new();
410
411        // Extract imports
412        self.extract_imports(
413            tree,
414            source,
415            file_path,
416            &mut cursor,
417            &mut relations,
418            compiled,
419        );
420
421        // Extract calls
422        self.extract_calls(
423            tree,
424            source,
425            file_path,
426            symbols,
427            &mut cursor,
428            &mut relations,
429            compiled,
430        );
431
432        relations
433    }
434
435    fn extract_imports(
436        &self,
437        tree: &Tree,
438        source: &str,
439        file_path: &str,
440        cursor: &mut QueryCursor,
441        relations: &mut Vec<SymbolRelation>,
442        compiled: &CompiledQueries,
443    ) {
444        let import_capture_names = compiled.imports.capture_names();
445        let source_bytes = source.as_bytes();
446        let file_id = format!("{}:__file__", file_path);
447        let matches = cursor.matches(&compiled.imports, tree.root_node(), source_bytes);
448
449        for match_ in matches {
450            let mut module_name: Option<&str> = None;
451            let mut line: u32 = 0;
452
453            for capture in match_.captures {
454                let Some(capture_name) = import_capture_names.get(capture.index as usize) else {
455                    warn!(
456                        "Import capture index out of bounds (index={} file='{}')",
457                        capture.index, file_path
458                    );
459                    continue;
460                };
461
462                let Some(text) = Self::node_text(capture.node, source_bytes, file_path) else {
463                    continue;
464                };
465
466                let capture_name = *capture_name;
467                match capture_name {
468                    "source" | "module" | "import.source" => {
469                        // Remove quotes from module name
470                        module_name = Some(text.trim_matches(|c| c == '"' || c == '\''));
471                        line = capture.node.start_position().row as u32 + 1;
472                    }
473                    _ => {}
474                }
475            }
476
477            if let Some(module) = module_name {
478                // Create a file-level import relation
479                relations.push(SymbolRelation {
480                    from_id: file_id.clone(),
481                    to_name: module.to_string(),
482                    kind: RelationType::Imports,
483                    line,
484                });
485            }
486        }
487    }
488
489    fn extract_calls(
490        &self,
491        tree: &Tree,
492        source: &str,
493        file_path: &str,
494        symbols: &[CodeNode],
495        cursor: &mut QueryCursor,
496        relations: &mut Vec<SymbolRelation>,
497        compiled: &CompiledQueries,
498    ) {
499        let call_capture_names = compiled.calls.capture_names();
500        let source_bytes = source.as_bytes();
501        let file_id = format!("{}:__file__", file_path);
502        let matches = cursor.matches(&compiled.calls, tree.root_node(), source_bytes);
503
504        for match_ in matches {
505            let mut callee_name: Option<&str> = None;
506            let mut call_line: u32 = 0;
507
508            for capture in match_.captures {
509                let Some(capture_name) = call_capture_names.get(capture.index as usize) else {
510                    warn!(
511                        "Call capture index out of bounds (index={} file='{}')",
512                        capture.index, file_path
513                    );
514                    continue;
515                };
516
517                let Some(text) = Self::node_text(capture.node, source_bytes, file_path) else {
518                    continue;
519                };
520
521                let capture_name = *capture_name;
522                match capture_name {
523                    "callee" | "function" | "call.function" => {
524                        // Handle method calls like obj.method()
525                        callee_name = Some(text.rsplit('.').next().unwrap_or(text));
526                        call_line = capture.node.start_position().row as u32 + 1;
527                    }
528                    _ => {}
529                }
530            }
531
532            if let Some(callee) = callee_name {
533                // Find the enclosing function/method
534                let caller_id = self
535                    .find_enclosing_symbol(call_line, symbols)
536                    .map(|s| s.id.clone())
537                    .unwrap_or_else(|| file_id.clone());
538
539                relations.push(SymbolRelation {
540                    from_id: caller_id,
541                    to_name: callee.to_string(),
542                    kind: RelationType::Calls,
543                    line: call_line,
544                });
545            }
546        }
547    }
548
549    fn find_enclosing_symbol<'a>(
550        &self,
551        line: u32,
552        symbols: &'a [CodeNode],
553    ) -> Option<&'a CodeNode> {
554        symbols
555            .iter()
556            .filter(|s| s.line_start <= line && s.line_end >= line)
557            .min_by_key(|s| s.line_end - s.line_start) // Smallest enclosing
558    }
559
560    #[inline]
561    fn node_text<'a>(node: Node<'a>, source_bytes: &'a [u8], file_path: &str) -> Option<&'a str> {
562        match node.utf8_text(source_bytes) {
563            Ok(text) => Some(text),
564            Err(error) => {
565                warn!(
566                    "Skipping invalid UTF-8 capture in file '{}' at row {}: {}",
567                    file_path,
568                    node.start_position().row,
569                    error
570                );
571                None
572            }
573        }
574    }
575
576    #[inline]
577    fn first_line_signature<'a>(source: &'a str, node: Node<'_>) -> Option<&'a str> {
578        let tail = source.get(node.start_byte()..)?;
579        let signature = tail.lines().next()?.trim();
580        if signature.is_empty() {
581            None
582        } else {
583            Some(signature)
584        }
585    }
586
587    // ─────────────────────────────────────────────────────────────────────────
588    // Query Registry Helper
589    // Extracts S-expressions into reusable strings. Eliminates duplication across
590    // compile functions. Makes adding new languages trivial (just add compile_xxx).
591    // Aligns with Accessibility (easy extension) and Affordability (less code).
592    // ─────────────────────────────────────────────────────────────────────────
593
594    fn compile_queries(
595        language: Language,
596        symbols_query: &str,
597        imports_query: &str,
598        calls_query: &str,
599    ) -> Result<CompiledQueries> {
600        let symbols = Query::new(&language, symbols_query)
601            .map_err(|e| ParseError::QueryError(e.to_string()))?;
602        let imports = Query::new(&language, imports_query)
603            .map_err(|e| ParseError::QueryError(e.to_string()))?;
604        let calls = Query::new(&language, calls_query)
605            .map_err(|e| ParseError::QueryError(e.to_string()))?;
606
607        Ok(CompiledQueries {
608            symbols,
609            imports,
610            calls,
611            language,
612        })
613    }
614
615    fn compile_typescript_queries() -> Result<CompiledQueries> {
616        let language = tree_sitter_typescript::language_typescript();
617
618        let symbols_query = r#"
619            (function_declaration name: (identifier) @name) @function_def
620            (class_declaration name: (type_identifier) @name) @class_def
621            (method_definition name: (property_identifier) @name) @method_def
622            (interface_declaration name: (type_identifier) @name) @interface_def
623            (type_alias_declaration name: (type_identifier) @name) @interface_def
624        "#;
625
626        let imports_query = r#"
627            (import_statement
628                source: (string) @source)
629        "#;
630
631        let calls_query = r#"
632            (call_expression
633                function: (identifier) @callee)
634
635            (call_expression
636                function: (member_expression
637                    property: (property_identifier) @callee))
638        "#;
639
640        Self::compile_queries(language, symbols_query, imports_query, calls_query)
641    }
642
643    fn compile_rust_queries() -> Result<CompiledQueries> {
644        let language = tree_sitter_rust::language();
645
646        let symbols_query = r#"
647            (function_item name: (identifier) @name) @function_def
648            (struct_item name: (type_identifier) @name) @struct_def
649            (enum_item name: (type_identifier) @name) @enum_def
650            (trait_item name: (type_identifier) @name) @trait_def
651        "#;
652
653        let imports_query = r#"
654            (use_declaration) @source
655        "#;
656
657        let calls_query = r#"
658            (call_expression function: (identifier) @callee)
659            (call_expression function: (field_expression field: (field_identifier) @callee))
660        "#;
661
662        Self::compile_queries(language, symbols_query, imports_query, calls_query)
663    }
664
665    fn compile_python_queries() -> Result<CompiledQueries> {
666        let language = tree_sitter_python::language();
667
668        let symbols_query = r#"
669            (function_definition name: (identifier) @name) @function_def
670            (class_definition name: (identifier) @name) @class_def
671        "#;
672
673        let imports_query = r#"
674            (import_statement) @source
675            (import_from_statement) @source
676        "#;
677
678        let calls_query = r#"
679            (call function: (identifier) @callee)
680            (call function: (attribute attribute: (identifier) @callee))
681        "#;
682
683        Self::compile_queries(language, symbols_query, imports_query, calls_query)
684    }
685
686    fn compile_go_queries() -> Result<CompiledQueries> {
687        let language = tree_sitter_go::language();
688
689        let symbols_query = r#"
690            (function_declaration name: (identifier) @name) @function_def
691            (method_declaration name: (field_identifier) @name) @method_def
692            (type_declaration (type_spec name: (type_identifier) @name type: (struct_type))) @struct_def
693            (type_declaration (type_spec name: (type_identifier) @name type: (interface_type))) @interface_def
694        "#;
695
696        let imports_query = r#"
697            (import_spec path: (interpreted_string_literal) @source)
698        "#;
699
700        let calls_query = r#"
701            (call_expression function: (identifier) @callee)
702            (call_expression function: (selector_expression field: (field_identifier) @callee))
703        "#;
704
705        Self::compile_queries(language, symbols_query, imports_query, calls_query)
706    }
707
708    fn compile_java_queries() -> Result<CompiledQueries> {
709        let language = tree_sitter_java::language();
710
711        let symbols_query = r#"
712            (method_declaration name: (identifier) @name) @method_def
713            (class_declaration name: (identifier) @name) @class_def
714            (interface_declaration name: (identifier) @name) @interface_def
715            (constructor_declaration name: (identifier) @name) @function_def
716        "#;
717
718        let imports_query = r#"
719            (import_declaration) @source
720        "#;
721
722        let calls_query = r#"
723            (method_invocation name: (identifier) @callee)
724        "#;
725
726        Self::compile_queries(language, symbols_query, imports_query, calls_query)
727    }
728
729    fn compile_c_queries() -> Result<CompiledQueries> {
730        let language = tree_sitter_c::language();
731
732        let symbols_query = r#"
733            (function_definition declarator: (function_declarator declarator: (identifier) @name)) @function_def
734            (struct_specifier name: (type_identifier) @name) @struct_def
735            (enum_specifier name: (type_identifier) @name) @enum_def
736        "#;
737
738        let imports_query = r#"
739            (preproc_include path: (string_literal) @source)
740            (preproc_include path: (system_lib_string) @source)
741        "#;
742
743        let calls_query = r#"
744            (call_expression function: (identifier) @callee)
745        "#;
746
747        Self::compile_queries(language, symbols_query, imports_query, calls_query)
748    }
749
750    fn compile_cpp_queries() -> Result<CompiledQueries> {
751        let language = tree_sitter_cpp::language();
752
753        let symbols_query = r#"
754            (function_definition declarator: (function_declarator declarator: (identifier) @name)) @function_def
755            (function_definition declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name))) @method_def
756            (class_specifier name: (type_identifier) @name) @class_def
757            (struct_specifier name: (type_identifier) @name) @struct_def
758        "#;
759
760        let imports_query = r#"
761            (preproc_include path: (string_literal) @source)
762            (preproc_include path: (system_lib_string) @source)
763        "#;
764
765        let calls_query = r#"
766            (call_expression function: (identifier) @callee)
767            (call_expression function: (field_expression field: (field_identifier) @callee))
768        "#;
769
770        Self::compile_queries(language, symbols_query, imports_query, calls_query)
771    }
772
773    fn compile_csharp_queries() -> Result<CompiledQueries> {
774        let language = tree_sitter_c_sharp::language();
775
776        let symbols_query = r#"
777            (method_declaration name: (identifier) @name) @method_def
778            (class_declaration name: (identifier) @name) @class_def
779            (interface_declaration name: (identifier) @name) @interface_def
780            (struct_declaration name: (identifier) @name) @struct_def
781            (constructor_declaration name: (identifier) @name) @function_def
782            (property_declaration name: (identifier) @name) @method_def
783        "#;
784
785        let imports_query = r#"
786            (using_directive (identifier) @source)
787            (using_directive (qualified_name) @source)
788        "#;
789
790        let calls_query = r#"
791            (invocation_expression function: (identifier) @callee)
792            (invocation_expression function: (member_access_expression name: (identifier) @callee))
793        "#;
794
795        Self::compile_queries(language, symbols_query, imports_query, calls_query)
796    }
797}
798
799// ─────────────────────────────────────────────────────────────────────────────
800// Tests
801// ─────────────────────────────────────────────────────────────────────────────
802
803#[cfg(test)]
804mod tests {
805    use super::*;
806
807    #[test]
808    fn test_parser_initialization() {
809        // This test will show us the actual error if query compilation fails
810        match ArborParser::new() {
811            Ok(_) => println!("Parser initialized successfully!"),
812            Err(e) => panic!("Parser failed to initialize: {}", e),
813        }
814    }
815
816    #[test]
817    fn test_parse_typescript_symbols() {
818        let mut parser = ArborParser::new().unwrap();
819
820        let source = r#"
821            function greet(name: string): string {
822                return `Hello, ${name}!`;
823            }
824
825            export class UserService {
826                validate(user: User): boolean {
827                    return true;
828                }
829            }
830
831            interface User {
832                name: string;
833                email: string;
834            }
835        "#;
836
837        let result = parser.parse_source(source, "test.ts", "ts").unwrap();
838
839        assert!(result.symbols.iter().any(|s| s.name == "greet"));
840        assert!(result.symbols.iter().any(|s| s.name == "UserService"));
841        assert!(result.symbols.iter().any(|s| s.name == "validate"));
842        assert!(result.symbols.iter().any(|s| s.name == "User"));
843    }
844
845    #[test]
846    fn test_parse_typescript_imports() {
847        let mut parser = ArborParser::new().unwrap();
848
849        let source = r#"
850            import { useState } from 'react';
851            import lodash from 'lodash';
852
853            function Component() {
854                const [count, setCount] = useState(0);
855            }
856        "#;
857
858        let result = parser.parse_source(source, "test.ts", "ts").unwrap();
859
860        let imports: Vec<_> = result
861            .relations
862            .iter()
863            .filter(|r| r.kind == RelationType::Imports)
864            .collect();
865
866        assert!(imports.iter().any(|i| i.to_name.contains("react")));
867        assert!(imports.iter().any(|i| i.to_name.contains("lodash")));
868    }
869
870    #[test]
871    fn test_parse_typescript_calls() {
872        let mut parser = ArborParser::new().unwrap();
873
874        let source = r#"
875            function outer() {
876                inner();
877                helper.process();
878            }
879
880            function inner() {
881                console.log("Hello");
882            }
883        "#;
884
885        let result = parser.parse_source(source, "test.ts", "ts").unwrap();
886
887        let calls: Vec<_> = result
888            .relations
889            .iter()
890            .filter(|r| r.kind == RelationType::Calls)
891            .collect();
892
893        assert!(calls.iter().any(|c| c.to_name == "inner"));
894        assert!(calls.iter().any(|c| c.to_name == "process"));
895        assert!(calls.iter().any(|c| c.to_name == "log"));
896    }
897
898    #[test]
899    fn test_parse_rust_symbols() {
900        let mut parser = ArborParser::new().unwrap();
901
902        let source = r#"
903            fn main() {
904                println!("Hello!");
905            }
906
907            pub struct User {
908                name: String,
909            }
910
911            impl User {
912                fn new(name: &str) -> Self {
913                    Self { name: name.to_string() }
914                }
915            }
916
917            enum Status {
918                Active,
919                Inactive,
920            }
921        "#;
922
923        let result = parser.parse_source(source, "test.rs", "rs").unwrap();
924
925        assert!(result.symbols.iter().any(|s| s.name == "main"));
926        assert!(result.symbols.iter().any(|s| s.name == "User"));
927        assert!(result.symbols.iter().any(|s| s.name == "new"));
928        assert!(result.symbols.iter().any(|s| s.name == "Status"));
929    }
930
931    #[test]
932    fn test_parse_python_symbols() {
933        let mut parser = ArborParser::new().unwrap();
934
935        let source = r#"
936def greet(name):
937    return f"Hello, {name}!"
938
939class UserService:
940    def validate(self, user):
941        return True
942        "#;
943
944        let result = parser.parse_source(source, "test.py", "py").unwrap();
945
946        assert!(result.symbols.iter().any(|s| s.name == "greet"));
947        assert!(result.symbols.iter().any(|s| s.name == "UserService"));
948        assert!(result.symbols.iter().any(|s| s.name == "validate"));
949    }
950
951    #[test]
952    fn test_parse_fallback_kotlin_symbols() {
953        let mut parser = ArborParser::new().unwrap();
954
955        let source = r#"
956            class BillingService
957            fun computeInvoiceTotal(amount: Double): Double = amount
958        "#;
959
960        let result = parser.parse_source(source, "billing.kt", "kt").unwrap();
961
962        assert!(result.symbols.iter().any(|s| s.name == "BillingService"));
963        assert!(result
964            .symbols
965            .iter()
966            .any(|s| s.name == "computeInvoiceTotal"));
967        assert!(result.relations.is_empty());
968    }
969
970    #[test]
971    fn test_parse_go_symbols() {
972        let mut parser = ArborParser::new().unwrap();
973
974        let source = r#"
975package main
976
977import "fmt"
978
979func greet(name string) string {
980    return fmt.Sprintf("Hello, %s!", name)
981}
982
983type User struct {
984    Name string
985    Age  int
986}
987
988type Service interface {
989    Process(data []byte) error
990}
991"#;
992
993        let result = parser.parse_source(source, "main.go", "go").unwrap();
994
995        assert!(result.symbols.iter().any(|s| s.name == "greet"));
996        assert!(result.symbols.iter().any(|s| s.name == "User"));
997        assert!(result.symbols.iter().any(|s| s.name == "Service"));
998    }
999
1000    #[test]
1001    fn test_parse_java_symbols() {
1002        let mut parser = ArborParser::new().unwrap();
1003
1004        let source = r#"
1005package com.example;
1006
1007import java.util.List;
1008
1009public class OrderService {
1010    public void processOrder(String orderId) {
1011        validate(orderId);
1012    }
1013
1014    private void validate(String id) {
1015    }
1016}
1017"#;
1018
1019        let result = parser
1020            .parse_source(source, "OrderService.java", "java")
1021            .unwrap();
1022
1023        assert!(result.symbols.iter().any(|s| s.name == "OrderService"));
1024        assert!(result.symbols.iter().any(|s| s.name == "processOrder"));
1025        assert!(result.symbols.iter().any(|s| s.name == "validate"));
1026    }
1027
1028    #[test]
1029    fn test_parse_c_symbols() {
1030        let mut parser = ArborParser::new().unwrap();
1031
1032        let source = r#"
1033#include <stdio.h>
1034
1035struct Point {
1036    int x;
1037    int y;
1038};
1039
1040void print_point(struct Point p) {
1041    printf("(%d, %d)\n", p.x, p.y);
1042}
1043
1044int add(int a, int b) {
1045    return a + b;
1046}
1047"#;
1048
1049        let result = parser.parse_source(source, "math.c", "c").unwrap();
1050
1051        assert!(result.symbols.iter().any(|s| s.name == "Point"));
1052        assert!(result.symbols.iter().any(|s| s.name == "print_point"));
1053        assert!(result.symbols.iter().any(|s| s.name == "add"));
1054    }
1055
1056    #[test]
1057    fn test_parse_cpp_symbols() {
1058        let mut parser = ArborParser::new().unwrap();
1059
1060        let source = r#"
1061#include <iostream>
1062
1063class Calculator {
1064public:
1065    int add(int a, int b) {
1066        return a + b;
1067    }
1068};
1069
1070struct Config {
1071    int timeout;
1072};
1073
1074void helpers() {
1075    std::cout << "ok" << std::endl;
1076}
1077"#;
1078
1079        let result = parser.parse_source(source, "calc.cpp", "cpp").unwrap();
1080
1081        assert!(result.symbols.iter().any(|s| s.name == "Calculator"));
1082        assert!(result.symbols.iter().any(|s| s.name == "Config"));
1083        assert!(result.symbols.iter().any(|s| s.name == "helpers"));
1084    }
1085
1086    #[test]
1087    fn test_parse_csharp_symbols() {
1088        let mut parser = ArborParser::new().unwrap();
1089
1090        let source = r#"
1091using System;
1092
1093namespace MyApp
1094{
1095    public class UserController
1096    {
1097        public string GetUser(int id)
1098        {
1099            return "user";
1100        }
1101    }
1102
1103    public interface IRepository
1104    {
1105        void Save(string data);
1106    }
1107}
1108"#;
1109
1110        let result = parser
1111            .parse_source(source, "UserController.cs", "cs")
1112            .unwrap();
1113
1114        assert!(result.symbols.iter().any(|s| s.name == "UserController"));
1115        assert!(result.symbols.iter().any(|s| s.name == "GetUser"));
1116        assert!(result.symbols.iter().any(|s| s.name == "IRepository"));
1117        assert!(result.symbols.iter().any(|s| s.name == "Save"));
1118    }
1119
1120    #[test]
1121    fn test_parse_unsupported_extension_errors() {
1122        let mut parser = ArborParser::new().unwrap();
1123        let result = parser.parse_source("anything", "test.xyz", "xyz");
1124        assert!(result.is_err());
1125    }
1126
1127    #[test]
1128    fn test_parse_result_file_path() {
1129        let mut parser = ArborParser::new().unwrap();
1130        let result = parser
1131            .parse_source("fn main() {}", "test.rs", "rs")
1132            .unwrap();
1133        assert_eq!(result.file_path, "test.rs");
1134    }
1135
1136    #[test]
1137    fn test_parse_python_imports_detected() {
1138        let mut parser = ArborParser::new().unwrap();
1139
1140        let source = r#"
1141import os
1142from pathlib import Path
1143
1144def read_file(path):
1145    with open(path) as f:
1146        return f.read()
1147"#;
1148
1149        let result = parser.parse_source(source, "utils.py", "py").unwrap();
1150        assert!(result.symbols.iter().any(|s| s.name == "read_file"));
1151        assert!(result
1152            .relations
1153            .iter()
1154            .any(|r| r.kind == RelationType::Imports));
1155    }
1156}