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 tree_sitter::{Language, Parser, Query, QueryCursor, Tree};
17
18// ─────────────────────────────────────────────────────────────────────────────
19// Types
20// ─────────────────────────────────────────────────────────────────────────────
21
22/// A relationship between two symbols in the code.
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct SymbolRelation {
25    /// The source symbol (caller/importer).
26    pub from_id: String,
27    /// The target symbol name (what is being called/imported).
28    pub to_name: String,
29    /// The type of relationship.
30    pub kind: RelationType,
31    /// Line number where the relationship occurs.
32    pub line: u32,
33}
34
35/// Types of relationships between code symbols.
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37pub enum RelationType {
38    /// Function/method calls another function.
39    Calls,
40    /// Module imports another module or symbol.
41    Imports,
42    /// Class extends another class.
43    Extends,
44    /// Class/type implements an interface.
45    Implements,
46}
47
48/// Result of parsing a single file.
49#[derive(Debug)]
50pub struct ParseResult {
51    /// Extracted code symbols.
52    pub symbols: Vec<CodeNode>,
53    /// Relationships between symbols.
54    pub relations: Vec<SymbolRelation>,
55    /// File path that was parsed.
56    pub file_path: String,
57}
58
59// ─────────────────────────────────────────────────────────────────────────────
60// ArborParser
61// ─────────────────────────────────────────────────────────────────────────────
62
63/// High-performance code parser using Tree-sitter queries.
64///
65/// The parser caches compiled queries for reuse across multiple files,
66/// making it efficient for large codebase indexing.
67pub struct ArborParser {
68    /// Tree-sitter parser instance.
69    parser: Parser,
70    /// Compiled queries by language.
71    queries: HashMap<String, CompiledQueries>,
72}
73
74/// Pre-compiled queries for a specific language.
75struct CompiledQueries {
76    /// Query for extracting symbols (functions, classes, etc.).
77    symbols: Query,
78    /// Query for extracting imports.
79    imports: Query,
80    /// Query for extracting function calls.
81    calls: Query,
82    /// The language for this query set.
83    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    /// Creates a new ArborParser with pre-compiled queries.
94    ///
95    /// Returns an error if any language queries fail to compile.
96    pub fn new() -> Result<Self> {
97        let parser = Parser::new();
98        let mut queries = HashMap::new();
99
100        // Compile TypeScript/JavaScript queries
101        for ext in &["ts", "tsx", "js", "jsx"] {
102            // We need to recompile for each since Query doesn't implement Clone
103            let compiled = Self::compile_typescript_queries()?;
104            queries.insert(ext.to_string(), compiled);
105        }
106
107        // Compile Rust queries
108        let rs_queries = Self::compile_rust_queries()?;
109        queries.insert("rs".to_string(), rs_queries);
110
111        // Compile Python queries
112        let py_queries = Self::compile_python_queries()?;
113        queries.insert("py".to_string(), py_queries);
114
115        // TEMPORARILY DISABLED FOR DEBUGGING
116        // Compile Go queries
117        let go_queries = Self::compile_go_queries()?;
118        queries.insert("go".to_string(), go_queries);
119
120        // Compile Java queries
121        let java_queries = Self::compile_java_queries()?;
122        queries.insert("java".to_string(), java_queries);
123
124        // Compile C queries
125        for ext in &["c", "h"] {
126            queries.insert(ext.to_string(), Self::compile_c_queries()?);
127        }
128
129        // Compile C++ queries
130        for ext in &["cpp", "hpp", "cc", "hh", "cxx"] {
131            queries.insert(ext.to_string(), Self::compile_cpp_queries()?);
132        }
133
134        // NOTE: Dart queries disabled due to tree-sitter-dart 0.20 incompatibility with
135        // query syntax in tree-sitter 0.22. Dart is still supported via legacy parser.rs path.
136        // TODO: Upgrade when tree-sitter-dart releases 0.22 compatible version.
137
138        // Compile C# queries
139        let csharp_queries = Self::compile_csharp_queries()?;
140        queries.insert("cs".to_string(), csharp_queries);
141
142        Ok(Self { parser, queries })
143    }
144
145    /// Parses a file and extracts symbols and relationships.
146    ///
147    /// This is the main entry point for parsing. It returns a ParseResult
148    /// containing all symbols and their relationships, ready to be inserted
149    /// into an ArborGraph.
150    ///
151    /// # Errors
152    ///
153    /// Returns an error if the file cannot be read, the language is unsupported,
154    /// or parsing fails. Syntax errors in the source code are handled gracefully -
155    /// the parser will still extract what it can.
156    pub fn parse_file(&mut self, path: &Path) -> Result<ParseResult> {
157        // Read the file
158        let source = fs::read_to_string(path).map_err(|e| ParseError::io(path, e))?;
159
160        if source.is_empty() {
161            return Err(ParseError::EmptyFile(path.to_path_buf()));
162        }
163
164        // Get the extension (normalize to lowercase)
165        let ext = path
166            .extension()
167            .and_then(|e| e.to_str())
168            .ok_or_else(|| ParseError::UnsupportedLanguage(path.to_path_buf()))?;
169        let ext = ext.to_ascii_lowercase();
170
171        // Get compiled queries
172        let compiled = match self.queries.get(&ext) {
173            Some(compiled) => compiled,
174            None => {
175                if fallback_parser::is_fallback_supported_extension(&ext) {
176                    return Ok(ParseResult {
177                        symbols: fallback_parser::parse_fallback_source(
178                            &source,
179                            &path.to_string_lossy(),
180                            &ext,
181                        ),
182                        relations: Vec::new(),
183                        file_path: path.to_string_lossy().to_string(),
184                    });
185                }
186                return Err(ParseError::UnsupportedLanguage(path.to_path_buf()));
187            }
188        };
189
190        // Configure parser for this language
191        self.parser
192            .set_language(&compiled.language)
193            .map_err(|e| ParseError::ParserError(format!("Failed to set language: {}", e)))?;
194
195        // Parse the source
196        let tree = self
197            .parser
198            .parse(&source, None)
199            .ok_or_else(|| ParseError::ParserError("Tree-sitter returned no tree".into()))?;
200
201        let file_path = path.to_string_lossy().to_string();
202        let file_name = path
203            .file_name()
204            .and_then(|n| n.to_str())
205            .unwrap_or("unknown");
206
207        // Extract symbols
208        let symbols = self.extract_symbols(&tree, &source, &file_path, file_name, compiled);
209
210        // Extract relationships
211        let relations = self.extract_relations(&tree, &source, &file_path, &symbols, compiled);
212
213        Ok(ParseResult {
214            symbols,
215            relations,
216            file_path,
217        })
218    }
219
220    /// Parses source code directly (for testing or in-memory content).
221    pub fn parse_source(
222        &mut self,
223        source: &str,
224        file_path: &str,
225        language: &str,
226    ) -> Result<ParseResult> {
227        if source.is_empty() {
228            return Err(ParseError::EmptyFile(file_path.into()));
229        }
230
231        // Normalize language/extension to lowercase
232        let language = language.to_ascii_lowercase();
233        let compiled = self.queries.get(&language);
234
235        if compiled.is_none() {
236            if fallback_parser::is_fallback_supported_extension(&language) {
237                return Ok(ParseResult {
238                    symbols: fallback_parser::parse_fallback_source(source, file_path, &language),
239                    relations: Vec::new(),
240                    file_path: file_path.to_string(),
241                });
242            }
243            return Err(ParseError::UnsupportedLanguage(file_path.into()));
244        }
245
246        let compiled = compiled.unwrap();
247
248        self.parser
249            .set_language(&compiled.language)
250            .map_err(|e| ParseError::ParserError(format!("Failed to set language: {}", e)))?;
251
252        let tree = self
253            .parser
254            .parse(source, None)
255            .ok_or_else(|| ParseError::ParserError("Tree-sitter returned no tree".into()))?;
256
257        let file_name = Path::new(file_path)
258            .file_name()
259            .and_then(|n| n.to_str())
260            .unwrap_or("unknown");
261
262        let symbols = self.extract_symbols(&tree, source, file_path, file_name, compiled);
263        let relations = self.extract_relations(&tree, source, file_path, &symbols, compiled);
264
265        Ok(ParseResult {
266            symbols,
267            relations,
268            file_path: file_path.to_string(),
269        })
270    }
271
272    // ─────────────────────────────────────────────────────────────────────────
273    // Symbol Extraction
274    // ─────────────────────────────────────────────────────────────────────────
275
276    fn extract_symbols(
277        &self,
278        tree: &Tree,
279        source: &str,
280        file_path: &str,
281        file_name: &str,
282        compiled: &CompiledQueries,
283    ) -> Vec<CodeNode> {
284        let mut symbols = Vec::new();
285        let mut cursor = QueryCursor::new();
286
287        let matches = cursor.matches(&compiled.symbols, tree.root_node(), source.as_bytes());
288
289        for match_ in matches {
290            // Extract name and type from captures
291            let mut name: Option<&str> = None;
292            let mut kind: Option<NodeKind> = None;
293            let mut node = match_.captures.first().map(|c| c.node);
294
295            for capture in match_.captures {
296                let capture_name = compiled.symbols.capture_names()[capture.index as usize];
297                let text = capture.node.utf8_text(source.as_bytes()).unwrap_or("");
298
299                match capture_name {
300                    "name" | "function.name" | "class.name" | "interface.name" | "method.name" => {
301                        name = Some(text);
302                    }
303                    "function" | "function_def" => {
304                        kind = Some(NodeKind::Function);
305                        node = Some(capture.node);
306                    }
307                    "class" | "class_def" => {
308                        kind = Some(NodeKind::Class);
309                        node = Some(capture.node);
310                    }
311                    "interface" | "interface_def" => {
312                        kind = Some(NodeKind::Interface);
313                        node = Some(capture.node);
314                    }
315                    "method" | "method_def" => {
316                        kind = Some(NodeKind::Method);
317                        node = Some(capture.node);
318                    }
319                    "struct" | "struct_def" => {
320                        kind = Some(NodeKind::Struct);
321                        node = Some(capture.node);
322                    }
323                    "enum" | "enum_def" => {
324                        kind = Some(NodeKind::Enum);
325                        node = Some(capture.node);
326                    }
327                    "trait" | "trait_def" => {
328                        kind = Some(NodeKind::Interface);
329                        node = Some(capture.node);
330                    }
331                    _ => {}
332                }
333            }
334
335            if let (Some(name), Some(kind), Some(node)) = (name, kind, node) {
336                // Build fully qualified name: filename:symbol_name
337                let qualified_name = format!("{}:{}", file_name, name);
338
339                // Extract signature (first line of the node)
340                let signature = source
341                    .lines()
342                    .nth(node.start_position().row)
343                    .map(|s| s.trim().to_string());
344
345                let mut symbol = CodeNode::new(name, &qualified_name, kind, file_path)
346                    .with_lines(
347                        node.start_position().row as u32 + 1,
348                        node.end_position().row as u32 + 1,
349                    )
350                    .with_column(node.start_position().column as u32)
351                    .with_bytes(node.start_byte() as u32, node.end_byte() as u32);
352
353                if let Some(sig) = signature {
354                    symbol = symbol.with_signature(sig);
355                }
356
357                symbols.push(symbol);
358            }
359        }
360
361        symbols
362    }
363
364    // ─────────────────────────────────────────────────────────────────────────
365    // Relationship Extraction
366    // ─────────────────────────────────────────────────────────────────────────
367
368    fn extract_relations(
369        &self,
370        tree: &Tree,
371        source: &str,
372        file_path: &str,
373        symbols: &[CodeNode],
374        compiled: &CompiledQueries,
375    ) -> Vec<SymbolRelation> {
376        let mut relations = Vec::new();
377
378        // Extract imports
379        self.extract_imports(tree, source, file_path, &mut relations, compiled);
380
381        // Extract calls
382        self.extract_calls(tree, source, file_path, symbols, &mut relations, compiled);
383
384        relations
385    }
386
387    fn extract_imports(
388        &self,
389        tree: &Tree,
390        source: &str,
391        file_path: &str,
392        relations: &mut Vec<SymbolRelation>,
393        compiled: &CompiledQueries,
394    ) {
395        let mut cursor = QueryCursor::new();
396        let matches = cursor.matches(&compiled.imports, tree.root_node(), source.as_bytes());
397
398        for match_ in matches {
399            let mut module_name: Option<&str> = None;
400            let mut line: u32 = 0;
401
402            for capture in match_.captures {
403                let capture_name = compiled.imports.capture_names()[capture.index as usize];
404                let text = capture.node.utf8_text(source.as_bytes()).unwrap_or("");
405
406                match capture_name {
407                    "source" | "module" | "import.source" => {
408                        // Remove quotes from module name
409                        module_name = Some(text.trim_matches(|c| c == '"' || c == '\''));
410                        line = capture.node.start_position().row as u32 + 1;
411                    }
412                    _ => {}
413                }
414            }
415
416            if let Some(module) = module_name {
417                // Create a file-level import relation
418                let file_id = format!("{}:__file__", file_path);
419                relations.push(SymbolRelation {
420                    from_id: file_id,
421                    to_name: module.to_string(),
422                    kind: RelationType::Imports,
423                    line,
424                });
425            }
426        }
427    }
428
429    fn extract_calls(
430        &self,
431        tree: &Tree,
432        source: &str,
433        file_path: &str,
434        symbols: &[CodeNode],
435        relations: &mut Vec<SymbolRelation>,
436        compiled: &CompiledQueries,
437    ) {
438        let mut cursor = QueryCursor::new();
439        let matches = cursor.matches(&compiled.calls, tree.root_node(), source.as_bytes());
440
441        for match_ in matches {
442            let mut callee_name: Option<&str> = None;
443            let mut call_line: u32 = 0;
444
445            for capture in match_.captures {
446                let capture_name = compiled.calls.capture_names()[capture.index as usize];
447                let text = capture.node.utf8_text(source.as_bytes()).unwrap_or("");
448
449                match capture_name {
450                    "callee" | "function" | "call.function" => {
451                        // Handle method calls like obj.method()
452                        if let Some(dot_pos) = text.rfind('.') {
453                            callee_name = Some(&text[dot_pos + 1..]);
454                        } else {
455                            callee_name = Some(text);
456                        }
457                        call_line = capture.node.start_position().row as u32 + 1;
458                    }
459                    _ => {}
460                }
461            }
462
463            if let Some(callee) = callee_name {
464                // Find the enclosing function/method
465                let caller_id = self
466                    .find_enclosing_symbol(call_line, symbols)
467                    .map(|s| s.id.clone())
468                    .unwrap_or_else(|| format!("{}:__file__", file_path));
469
470                relations.push(SymbolRelation {
471                    from_id: caller_id,
472                    to_name: callee.to_string(),
473                    kind: RelationType::Calls,
474                    line: call_line,
475                });
476            }
477        }
478    }
479
480    fn find_enclosing_symbol<'a>(
481        &self,
482        line: u32,
483        symbols: &'a [CodeNode],
484    ) -> Option<&'a CodeNode> {
485        symbols
486            .iter()
487            .filter(|s| s.line_start <= line && s.line_end >= line)
488            .min_by_key(|s| s.line_end - s.line_start) // Smallest enclosing
489    }
490
491    // ─────────────────────────────────────────────────────────────────────────
492    // Query Compilation
493    // ─────────────────────────────────────────────────────────────────────────
494
495    fn compile_typescript_queries() -> Result<CompiledQueries> {
496        let language = tree_sitter_typescript::language_typescript();
497
498        // Symbol extraction query - simplified for tree-sitter-typescript 0.20
499        let symbols_query = r#"
500            (function_declaration name: (identifier) @name) @function_def
501            (class_declaration name: (type_identifier) @name) @class_def
502            (method_definition name: (property_identifier) @name) @method_def
503            (interface_declaration name: (type_identifier) @name) @interface_def
504            (type_alias_declaration name: (type_identifier) @name) @interface_def
505        "#;
506
507        // Import query - simplified
508        let imports_query = r#"
509            (import_statement
510                source: (string) @source)
511        "#;
512
513        // Call expression query - simplified
514        let calls_query = r#"
515            (call_expression
516                function: (identifier) @callee)
517
518            (call_expression
519                function: (member_expression
520                    property: (property_identifier) @callee))
521        "#;
522
523        let symbols = Query::new(&language, symbols_query)
524            .map_err(|e| ParseError::QueryError(e.to_string()))?;
525        let imports = Query::new(&language, imports_query)
526            .map_err(|e| ParseError::QueryError(e.to_string()))?;
527        let calls = Query::new(&language, calls_query)
528            .map_err(|e| ParseError::QueryError(e.to_string()))?;
529
530        Ok(CompiledQueries {
531            symbols,
532            imports,
533            calls,
534            language,
535        })
536    }
537
538    fn compile_rust_queries() -> Result<CompiledQueries> {
539        let language = tree_sitter_rust::language();
540
541        // Simplified for tree-sitter-rust 0.20
542        let symbols_query = r#"
543            (function_item name: (identifier) @name) @function_def
544            (struct_item name: (type_identifier) @name) @struct_def
545            (enum_item name: (type_identifier) @name) @enum_def
546            (trait_item name: (type_identifier) @name) @trait_def
547        "#;
548
549        // Simplified imports query - just capture use_declaration
550        let imports_query = r#"
551            (use_declaration) @source
552        "#;
553
554        // Simplified calls query
555        let calls_query = r#"
556            (call_expression function: (identifier) @callee)
557            (call_expression function: (field_expression field: (field_identifier) @callee))
558        "#;
559
560        let symbols = Query::new(&language, symbols_query)
561            .map_err(|e| ParseError::QueryError(e.to_string()))?;
562        let imports = Query::new(&language, imports_query)
563            .map_err(|e| ParseError::QueryError(e.to_string()))?;
564        let calls = Query::new(&language, calls_query)
565            .map_err(|e| ParseError::QueryError(e.to_string()))?;
566
567        Ok(CompiledQueries {
568            symbols,
569            imports,
570            calls,
571            language,
572        })
573    }
574
575    fn compile_python_queries() -> Result<CompiledQueries> {
576        let language = tree_sitter_python::language();
577
578        // Simplified for tree-sitter-python 0.20
579        let symbols_query = r#"
580            (function_definition name: (identifier) @name) @function_def
581            (class_definition name: (identifier) @name) @class_def
582        "#;
583
584        // Simplified imports query
585        let imports_query = r#"
586            (import_statement) @source
587            (import_from_statement) @source
588        "#;
589
590        // Simplified calls query
591        let calls_query = r#"
592            (call function: (identifier) @callee)
593            (call function: (attribute attribute: (identifier) @callee))
594        "#;
595
596        let symbols = Query::new(&language, symbols_query)
597            .map_err(|e| ParseError::QueryError(e.to_string()))?;
598        let imports = Query::new(&language, imports_query)
599            .map_err(|e| ParseError::QueryError(e.to_string()))?;
600        let calls = Query::new(&language, calls_query)
601            .map_err(|e| ParseError::QueryError(e.to_string()))?;
602
603        Ok(CompiledQueries {
604            symbols,
605            imports,
606            calls,
607            language,
608        })
609    }
610
611    fn compile_go_queries() -> Result<CompiledQueries> {
612        let language = tree_sitter_go::language();
613
614        let symbols_query = r#"
615            (function_declaration name: (identifier) @name) @function_def
616            (method_declaration name: (field_identifier) @name) @method_def
617            (type_declaration (type_spec name: (type_identifier) @name type: (struct_type))) @struct_def
618            (type_declaration (type_spec name: (type_identifier) @name type: (interface_type))) @interface_def
619        "#;
620
621        let imports_query = r#"
622            (import_spec path: (interpreted_string_literal) @source)
623        "#;
624
625        let calls_query = r#"
626            (call_expression function: (identifier) @callee)
627            (call_expression function: (selector_expression field: (field_identifier) @callee))
628        "#;
629
630        let symbols = Query::new(&language, symbols_query)
631            .map_err(|e| ParseError::QueryError(e.to_string()))?;
632        let imports = Query::new(&language, imports_query)
633            .map_err(|e| ParseError::QueryError(e.to_string()))?;
634        let calls = Query::new(&language, calls_query)
635            .map_err(|e| ParseError::QueryError(e.to_string()))?;
636
637        Ok(CompiledQueries {
638            symbols,
639            imports,
640            calls,
641            language,
642        })
643    }
644
645    fn compile_java_queries() -> Result<CompiledQueries> {
646        let language = tree_sitter_java::language();
647
648        let symbols_query = r#"
649            (method_declaration name: (identifier) @name) @method_def
650            (class_declaration name: (identifier) @name) @class_def
651            (interface_declaration name: (identifier) @name) @interface_def
652            (constructor_declaration name: (identifier) @name) @function_def
653        "#;
654
655        let imports_query = r#"
656            (import_declaration) @source
657        "#;
658
659        let calls_query = r#"
660            (method_invocation name: (identifier) @callee)
661        "#;
662
663        let symbols = Query::new(&language, symbols_query)
664            .map_err(|e| ParseError::QueryError(e.to_string()))?;
665        let imports = Query::new(&language, imports_query)
666            .map_err(|e| ParseError::QueryError(e.to_string()))?;
667        let calls = Query::new(&language, calls_query)
668            .map_err(|e| ParseError::QueryError(e.to_string()))?;
669
670        Ok(CompiledQueries {
671            symbols,
672            imports,
673            calls,
674            language,
675        })
676    }
677
678    fn compile_c_queries() -> Result<CompiledQueries> {
679        let language = tree_sitter_c::language();
680
681        let symbols_query = r#"
682            (function_definition declarator: (function_declarator declarator: (identifier) @name)) @function_def
683            (struct_specifier name: (type_identifier) @name) @struct_def
684            (enum_specifier name: (type_identifier) @name) @enum_def
685        "#;
686
687        let imports_query = r#"
688            (preproc_include path: (string_literal) @source)
689            (preproc_include path: (system_lib_string) @source)
690        "#;
691
692        let calls_query = r#"
693            (call_expression function: (identifier) @callee)
694        "#;
695
696        let symbols = Query::new(&language, symbols_query)
697            .map_err(|e| ParseError::QueryError(e.to_string()))?;
698        let imports = Query::new(&language, imports_query)
699            .map_err(|e| ParseError::QueryError(e.to_string()))?;
700        let calls = Query::new(&language, calls_query)
701            .map_err(|e| ParseError::QueryError(e.to_string()))?;
702
703        Ok(CompiledQueries {
704            symbols,
705            imports,
706            calls,
707            language,
708        })
709    }
710
711    fn compile_cpp_queries() -> Result<CompiledQueries> {
712        let language = tree_sitter_cpp::language();
713
714        let symbols_query = r#"
715            (function_definition declarator: (function_declarator declarator: (identifier) @name)) @function_def
716            (function_definition declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name))) @method_def
717            (class_specifier name: (type_identifier) @name) @class_def
718            (struct_specifier name: (type_identifier) @name) @struct_def
719        "#;
720
721        let imports_query = r#"
722            (preproc_include path: (string_literal) @source)
723            (preproc_include path: (system_lib_string) @source)
724        "#;
725
726        let calls_query = r#"
727            (call_expression function: (identifier) @callee)
728            (call_expression function: (field_expression field: (field_identifier) @callee))
729        "#;
730
731        let symbols = Query::new(&language, symbols_query)
732            .map_err(|e| ParseError::QueryError(e.to_string()))?;
733        let imports = Query::new(&language, imports_query)
734            .map_err(|e| ParseError::QueryError(e.to_string()))?;
735        let calls = Query::new(&language, calls_query)
736            .map_err(|e| ParseError::QueryError(e.to_string()))?;
737
738        Ok(CompiledQueries {
739            symbols,
740            imports,
741            calls,
742            language,
743        })
744    }
745
746    fn compile_csharp_queries() -> Result<CompiledQueries> {
747        let language = tree_sitter_c_sharp::language();
748
749        let symbols_query = r#"
750            (method_declaration name: (identifier) @name) @method_def
751            (class_declaration name: (identifier) @name) @class_def
752            (interface_declaration name: (identifier) @name) @interface_def
753            (struct_declaration name: (identifier) @name) @struct_def
754            (constructor_declaration name: (identifier) @name) @function_def
755            (property_declaration name: (identifier) @name) @method_def
756        "#;
757
758        let imports_query = r#"
759            (using_directive (identifier) @source)
760            (using_directive (qualified_name) @source)
761        "#;
762
763        let calls_query = r#"
764            (invocation_expression function: (identifier) @callee)
765            (invocation_expression function: (member_access_expression name: (identifier) @callee))
766        "#;
767
768        let symbols = Query::new(&language, symbols_query)
769            .map_err(|e| ParseError::QueryError(e.to_string()))?;
770        let imports = Query::new(&language, imports_query)
771            .map_err(|e| ParseError::QueryError(e.to_string()))?;
772        let calls = Query::new(&language, calls_query)
773            .map_err(|e| ParseError::QueryError(e.to_string()))?;
774
775        Ok(CompiledQueries {
776            symbols,
777            imports,
778            calls,
779            language,
780        })
781    }
782
783    // NOTE: compile_dart_queries() removed - tree-sitter-dart 0.20 is incompatible
784    // with tree-sitter 0.22 query syntax. Dart is still supported via legacy parser.rs.
785    // TODO: Add Dart query support when tree-sitter-dart releases 0.22+ compatible version.
786}
787
788// ─────────────────────────────────────────────────────────────────────────────
789// Tests
790// ─────────────────────────────────────────────────────────────────────────────
791
792#[cfg(test)]
793mod tests {
794    use super::*;
795
796    #[test]
797    fn test_parser_initialization() {
798        // This test will show us the actual error if query compilation fails
799        match ArborParser::new() {
800            Ok(_) => println!("Parser initialized successfully!"),
801            Err(e) => panic!("Parser failed to initialize: {}", e),
802        }
803    }
804
805    #[test]
806    fn test_parse_typescript_symbols() {
807        let mut parser = ArborParser::new().unwrap();
808
809        let source = r#"
810            function greet(name: string): string {
811                return `Hello, ${name}!`;
812            }
813
814            export class UserService {
815                validate(user: User): boolean {
816                    return true;
817                }
818            }
819
820            interface User {
821                name: string;
822                email: string;
823            }
824        "#;
825
826        let result = parser.parse_source(source, "test.ts", "ts").unwrap();
827
828        assert!(result.symbols.iter().any(|s| s.name == "greet"));
829        assert!(result.symbols.iter().any(|s| s.name == "UserService"));
830        assert!(result.symbols.iter().any(|s| s.name == "validate"));
831        assert!(result.symbols.iter().any(|s| s.name == "User"));
832    }
833
834    #[test]
835    fn test_parse_typescript_imports() {
836        let mut parser = ArborParser::new().unwrap();
837
838        let source = r#"
839            import { useState } from 'react';
840            import lodash from 'lodash';
841
842            function Component() {
843                const [count, setCount] = useState(0);
844            }
845        "#;
846
847        let result = parser.parse_source(source, "test.ts", "ts").unwrap();
848
849        let imports: Vec<_> = result
850            .relations
851            .iter()
852            .filter(|r| r.kind == RelationType::Imports)
853            .collect();
854
855        assert!(imports.iter().any(|i| i.to_name.contains("react")));
856        assert!(imports.iter().any(|i| i.to_name.contains("lodash")));
857    }
858
859    #[test]
860    fn test_parse_typescript_calls() {
861        let mut parser = ArborParser::new().unwrap();
862
863        let source = r#"
864            function outer() {
865                inner();
866                helper.process();
867            }
868
869            function inner() {
870                console.log("Hello");
871            }
872        "#;
873
874        let result = parser.parse_source(source, "test.ts", "ts").unwrap();
875
876        let calls: Vec<_> = result
877            .relations
878            .iter()
879            .filter(|r| r.kind == RelationType::Calls)
880            .collect();
881
882        assert!(calls.iter().any(|c| c.to_name == "inner"));
883        assert!(calls.iter().any(|c| c.to_name == "process"));
884        assert!(calls.iter().any(|c| c.to_name == "log"));
885    }
886
887    #[test]
888    fn test_parse_rust_symbols() {
889        let mut parser = ArborParser::new().unwrap();
890
891        let source = r#"
892            fn main() {
893                println!("Hello!");
894            }
895
896            pub struct User {
897                name: String,
898            }
899
900            impl User {
901                fn new(name: &str) -> Self {
902                    Self { name: name.to_string() }
903                }
904            }
905
906            enum Status {
907                Active,
908                Inactive,
909            }
910        "#;
911
912        let result = parser.parse_source(source, "test.rs", "rs").unwrap();
913
914        assert!(result.symbols.iter().any(|s| s.name == "main"));
915        assert!(result.symbols.iter().any(|s| s.name == "User"));
916        assert!(result.symbols.iter().any(|s| s.name == "new"));
917        assert!(result.symbols.iter().any(|s| s.name == "Status"));
918    }
919
920    #[test]
921    fn test_parse_python_symbols() {
922        let mut parser = ArborParser::new().unwrap();
923
924        let source = r#"
925def greet(name):
926    return f"Hello, {name}!"
927
928class UserService:
929    def validate(self, user):
930        return True
931        "#;
932
933        let result = parser.parse_source(source, "test.py", "py").unwrap();
934
935        assert!(result.symbols.iter().any(|s| s.name == "greet"));
936        assert!(result.symbols.iter().any(|s| s.name == "UserService"));
937        assert!(result.symbols.iter().any(|s| s.name == "validate"));
938    }
939
940    #[test]
941    fn test_parse_fallback_kotlin_symbols() {
942        let mut parser = ArborParser::new().unwrap();
943
944        let source = r#"
945            class BillingService
946            fun computeInvoiceTotal(amount: Double): Double = amount
947        "#;
948
949        let result = parser.parse_source(source, "billing.kt", "kt").unwrap();
950
951        assert!(result.symbols.iter().any(|s| s.name == "BillingService"));
952        assert!(result
953            .symbols
954            .iter()
955            .any(|s| s.name == "computeInvoiceTotal"));
956        assert!(result.relations.is_empty());
957    }
958}