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