Skip to main content

aft/
parser.rs

1use std::cell::RefCell;
2use std::collections::{HashMap, HashSet};
3use std::path::{Path, PathBuf};
4use std::time::SystemTime;
5
6use streaming_iterator::StreamingIterator;
7use tree_sitter::{Language, Node, Parser, Query, QueryCursor, Tree};
8
9use crate::callgraph::resolve_module_path;
10use crate::error::AftError;
11use crate::symbols::{Range, Symbol, SymbolKind, SymbolMatch};
12
13const MAX_REEXPORT_DEPTH: usize = 10;
14
15// --- Query patterns embedded at compile time ---
16
17const TS_QUERY: &str = r#"
18;; function declarations
19(function_declaration
20  name: (identifier) @fn.name) @fn.def
21
22;; arrow functions assigned to const/let/var
23(lexical_declaration
24  (variable_declarator
25    name: (identifier) @arrow.name
26    value: (arrow_function) @arrow.body)) @arrow.def
27
28;; class declarations
29(class_declaration
30  name: (type_identifier) @class.name) @class.def
31
32;; method definitions inside classes
33(class_declaration
34  name: (type_identifier) @method.class_name
35  body: (class_body
36    (method_definition
37      name: (property_identifier) @method.name) @method.def))
38
39;; interface declarations
40(interface_declaration
41  name: (type_identifier) @interface.name) @interface.def
42
43;; enum declarations
44(enum_declaration
45  name: (identifier) @enum.name) @enum.def
46
47;; type alias declarations
48(type_alias_declaration
49  name: (type_identifier) @type_alias.name) @type_alias.def
50
51;; top-level const/let variable declarations
52(lexical_declaration
53  (variable_declarator
54    name: (identifier) @var.name)) @var.def
55
56;; export statement wrappers (top-level only)
57(export_statement) @export.stmt
58"#;
59
60const JS_QUERY: &str = r#"
61;; function declarations
62(function_declaration
63  name: (identifier) @fn.name) @fn.def
64
65;; arrow functions assigned to const/let/var
66(lexical_declaration
67  (variable_declarator
68    name: (identifier) @arrow.name
69    value: (arrow_function) @arrow.body)) @arrow.def
70
71;; class declarations
72(class_declaration
73  name: (identifier) @class.name) @class.def
74
75;; method definitions inside classes
76(class_declaration
77  name: (identifier) @method.class_name
78  body: (class_body
79    (method_definition
80      name: (property_identifier) @method.name) @method.def))
81
82;; top-level const/let variable declarations
83(lexical_declaration
84  (variable_declarator
85    name: (identifier) @var.name)) @var.def
86
87;; export statement wrappers (top-level only)
88(export_statement) @export.stmt
89"#;
90
91const PY_QUERY: &str = r#"
92;; function definitions (top-level and nested)
93(function_definition
94  name: (identifier) @fn.name) @fn.def
95
96;; class definitions
97(class_definition
98  name: (identifier) @class.name) @class.def
99
100;; decorated definitions (wraps function_definition or class_definition)
101(decorated_definition
102  (decorator) @dec.decorator) @dec.def
103"#;
104
105const RS_QUERY: &str = r#"
106;; free functions (with optional visibility)
107(function_item
108  name: (identifier) @fn.name) @fn.def
109
110;; struct items
111(struct_item
112  name: (type_identifier) @struct.name) @struct.def
113
114;; enum items
115(enum_item
116  name: (type_identifier) @enum.name) @enum.def
117
118;; trait items
119(trait_item
120  name: (type_identifier) @trait.name) @trait.def
121
122;; impl blocks — capture the whole block to find methods
123(impl_item) @impl.def
124
125;; visibility modifiers on any item
126(visibility_modifier) @vis.mod
127"#;
128
129const GO_QUERY: &str = r#"
130;; function declarations
131(function_declaration
132  name: (identifier) @fn.name) @fn.def
133
134;; method declarations (with receiver)
135(method_declaration
136  name: (field_identifier) @method.name) @method.def
137
138;; type declarations (struct and interface)
139(type_declaration
140  (type_spec
141    name: (type_identifier) @type.name
142    type: (_) @type.body)) @type.def
143"#;
144
145/// Supported language identifier.
146#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
147pub enum LangId {
148    TypeScript,
149    Tsx,
150    JavaScript,
151    Python,
152    Rust,
153    Go,
154    Markdown,
155}
156
157/// Maps file extension to language identifier.
158pub fn detect_language(path: &Path) -> Option<LangId> {
159    let ext = path.extension()?.to_str()?;
160    match ext {
161        "ts" => Some(LangId::TypeScript),
162        "tsx" => Some(LangId::Tsx),
163        "js" | "jsx" => Some(LangId::JavaScript),
164        "py" => Some(LangId::Python),
165        "rs" => Some(LangId::Rust),
166        "go" => Some(LangId::Go),
167        "md" | "markdown" | "mdx" => Some(LangId::Markdown),
168        _ => None,
169    }
170}
171
172/// Returns the tree-sitter Language grammar for a given LangId.
173pub fn grammar_for(lang: LangId) -> Language {
174    match lang {
175        LangId::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
176        LangId::Tsx => tree_sitter_typescript::LANGUAGE_TSX.into(),
177        LangId::JavaScript => tree_sitter_javascript::LANGUAGE.into(),
178        LangId::Python => tree_sitter_python::LANGUAGE.into(),
179        LangId::Rust => tree_sitter_rust::LANGUAGE.into(),
180        LangId::Go => tree_sitter_go::LANGUAGE.into(),
181        LangId::Markdown => tree_sitter_md::LANGUAGE.into(),
182    }
183}
184
185/// Returns the query pattern string for a given LangId, if implemented.
186fn query_for(lang: LangId) -> Option<&'static str> {
187    match lang {
188        LangId::TypeScript | LangId::Tsx => Some(TS_QUERY),
189        LangId::JavaScript => Some(JS_QUERY),
190        LangId::Python => Some(PY_QUERY),
191        LangId::Rust => Some(RS_QUERY),
192        LangId::Go => Some(GO_QUERY),
193        LangId::Markdown => None,
194    }
195}
196
197/// Cached parse result: mtime at parse time + the tree.
198struct CachedTree {
199    mtime: SystemTime,
200    tree: Tree,
201}
202
203/// Core parsing engine. Handles language detection, parse tree caching,
204/// and query pattern execution via tree-sitter.
205pub struct FileParser {
206    cache: HashMap<PathBuf, CachedTree>,
207}
208
209impl FileParser {
210    /// Create a new `FileParser` with an empty parse cache.
211    pub fn new() -> Self {
212        Self {
213            cache: HashMap::new(),
214        }
215    }
216
217    /// Parse a file, returning the tree and detected language. Uses cache if
218    /// the file hasn't been modified since last parse.
219    pub fn parse(&mut self, path: &Path) -> Result<(&Tree, LangId), AftError> {
220        let lang = detect_language(path).ok_or_else(|| AftError::InvalidRequest {
221            message: format!(
222                "unsupported file extension: {}",
223                path.extension()
224                    .and_then(|e| e.to_str())
225                    .unwrap_or("<none>")
226            ),
227        })?;
228
229        let canon = path.to_path_buf();
230        let current_mtime = std::fs::metadata(path)
231            .and_then(|m| m.modified())
232            .map_err(|e| AftError::FileNotFound {
233                path: format!("{}: {}", path.display(), e),
234            })?;
235
236        // Check cache validity
237        let needs_reparse = match self.cache.get(&canon) {
238            Some(cached) => cached.mtime != current_mtime,
239            None => true,
240        };
241
242        if needs_reparse {
243            let source = std::fs::read_to_string(path).map_err(|e| AftError::FileNotFound {
244                path: format!("{}: {}", path.display(), e),
245            })?;
246
247            let grammar = grammar_for(lang);
248            let mut parser = Parser::new();
249            parser.set_language(&grammar).map_err(|e| {
250                log::error!("grammar init failed for {:?}: {}", lang, e);
251                AftError::ParseError {
252                    message: format!("grammar init failed for {:?}: {}", lang, e),
253                }
254            })?;
255
256            let tree = parser.parse(&source, None).ok_or_else(|| {
257                log::error!("parse failed for {}", path.display());
258                AftError::ParseError {
259                    message: format!("tree-sitter parse returned None for {}", path.display()),
260                }
261            })?;
262
263            self.cache.insert(
264                canon.clone(),
265                CachedTree {
266                    mtime: current_mtime,
267                    tree,
268                },
269            );
270        }
271
272        let cached = self.cache.get(&canon).ok_or_else(|| AftError::ParseError {
273            message: format!("parser cache missing entry for {}", path.display()),
274        })?;
275        Ok((&cached.tree, lang))
276    }
277
278    /// Like [`FileParser::parse`] but returns an owned `Tree` clone.
279    ///
280    /// Useful when the caller needs to hold the tree while also calling
281    /// other mutable methods on this parser.
282    pub fn parse_cloned(&mut self, path: &Path) -> Result<(Tree, LangId), AftError> {
283        let (tree, lang) = self.parse(path)?;
284        Ok((tree.clone(), lang))
285    }
286
287    /// Extract symbols from a file using language-specific query patterns.
288    pub fn extract_symbols(&mut self, path: &Path) -> Result<Vec<Symbol>, AftError> {
289        let source = std::fs::read_to_string(path).map_err(|e| AftError::FileNotFound {
290            path: format!("{}: {}", path.display(), e),
291        })?;
292
293        let (tree, lang) = self.parse(path)?;
294        let root = tree.root_node();
295
296        // Markdown uses direct tree walking, not query patterns
297        if lang == LangId::Markdown {
298            return extract_md_symbols(&source, &root);
299        }
300
301        let query_src = query_for(lang).ok_or_else(|| AftError::InvalidRequest {
302            message: format!("no query patterns implemented for {:?} yet", lang),
303        })?;
304
305        let grammar = grammar_for(lang);
306        let query = Query::new(&grammar, query_src).map_err(|e| {
307            log::error!("query compile failed for {:?}: {}", lang, e);
308            AftError::ParseError {
309                message: format!("query compile error for {:?}: {}", lang, e),
310            }
311        })?;
312
313        match lang {
314            LangId::TypeScript | LangId::Tsx => extract_ts_symbols(&source, &root, &query),
315            LangId::JavaScript => extract_js_symbols(&source, &root, &query),
316            LangId::Python => extract_py_symbols(&source, &root, &query),
317            LangId::Rust => extract_rs_symbols(&source, &root, &query),
318            LangId::Go => extract_go_symbols(&source, &root, &query),
319            LangId::Markdown => Ok(vec![]), // handled by extract_md_symbols
320        }
321    }
322}
323
324/// Build a Range from a tree-sitter Node.
325pub(crate) fn node_range(node: &Node) -> Range {
326    let start = node.start_position();
327    let end = node.end_position();
328    Range {
329        start_line: start.row as u32,
330        start_col: start.column as u32,
331        end_line: end.row as u32,
332        end_col: end.column as u32,
333    }
334}
335
336/// Build a Range from a tree-sitter Node, expanding upward to include
337/// preceding attributes, decorators, and doc comments that belong to the symbol.
338///
339/// This ensures that when agents edit/replace a symbol, they get the full
340/// declaration including `#[test]`, `#[derive(...)]`, `/// doc`, `@decorator`, etc.
341pub(crate) fn node_range_with_decorators(node: &Node, source: &str, lang: LangId) -> Range {
342    let mut range = node_range(node);
343
344    let mut current = *node;
345    while let Some(prev) = current.prev_sibling() {
346        let kind = prev.kind();
347        let should_include = match lang {
348            LangId::Rust => {
349                // Include #[...] attributes
350                kind == "attribute_item"
351                    // Include /// doc comments (but not regular // comments)
352                    || (kind == "line_comment"
353                        && node_text(source, &prev).starts_with("///"))
354                    // Include /** ... */ doc comments
355                    || (kind == "block_comment"
356                        && node_text(source, &prev).starts_with("/**"))
357            }
358            LangId::TypeScript | LangId::Tsx | LangId::JavaScript => {
359                // Include @decorator
360                kind == "decorator"
361                    // Include /** JSDoc */ comments
362                    || (kind == "comment"
363                        && node_text(source, &prev).starts_with("/**"))
364            }
365            LangId::Go => {
366                // Include doc comments only if immediately above (no blank line gap)
367                kind == "comment" && is_adjacent_line(&prev, &current, source)
368            }
369            LangId::Python => {
370                // Decorators are handled by decorated_definition capture
371                false
372            }
373            LangId::Markdown => false,
374        };
375
376        if should_include {
377            range.start_line = prev.start_position().row as u32;
378            range.start_col = prev.start_position().column as u32;
379            current = prev;
380        } else {
381            break;
382        }
383    }
384
385    range
386}
387
388/// Check if two nodes are on adjacent lines (no blank line between them).
389fn is_adjacent_line(upper: &Node, lower: &Node, source: &str) -> bool {
390    let upper_end = upper.end_position().row;
391    let lower_start = lower.start_position().row;
392
393    if lower_start == 0 || lower_start <= upper_end {
394        return true;
395    }
396
397    // Check that there's no blank line between them
398    let lines: Vec<&str> = source.lines().collect();
399    for row in (upper_end + 1)..lower_start {
400        if row < lines.len() && lines[row].trim().is_empty() {
401            return false;
402        }
403    }
404    true
405}
406
407/// Extract the text of a node from source.
408pub(crate) fn node_text<'a>(source: &'a str, node: &Node) -> &'a str {
409    &source[node.byte_range()]
410}
411
412fn lexical_declaration_has_function_value(node: &Node) -> bool {
413    let mut cursor = node.walk();
414    if !cursor.goto_first_child() {
415        return false;
416    }
417
418    loop {
419        let child = cursor.node();
420        if matches!(
421            child.kind(),
422            "arrow_function" | "function_expression" | "generator_function"
423        ) {
424            return true;
425        }
426
427        if lexical_declaration_has_function_value(&child) {
428            return true;
429        }
430
431        if !cursor.goto_next_sibling() {
432            break;
433        }
434    }
435
436    false
437}
438
439/// Collect byte ranges of all export_statement nodes from query matches.
440fn collect_export_ranges(source: &str, root: &Node, query: &Query) -> Vec<std::ops::Range<usize>> {
441    let export_idx = query
442        .capture_names()
443        .iter()
444        .position(|n| *n == "export.stmt");
445    let export_idx = match export_idx {
446        Some(i) => i as u32,
447        None => return vec![],
448    };
449
450    let mut cursor = QueryCursor::new();
451    let mut ranges = Vec::new();
452    let mut matches = cursor.matches(query, *root, source.as_bytes());
453
454    while let Some(m) = {
455        matches.advance();
456        matches.get()
457    } {
458        for cap in m.captures {
459            if cap.index == export_idx {
460                ranges.push(cap.node.byte_range());
461            }
462        }
463    }
464    ranges
465}
466
467/// Check if a node's byte range is contained within any export statement.
468fn is_exported(node: &Node, export_ranges: &[std::ops::Range<usize>]) -> bool {
469    let r = node.byte_range();
470    export_ranges
471        .iter()
472        .any(|er| er.start <= r.start && r.end <= er.end)
473}
474
475/// Extract the first line of a node as its signature.
476fn extract_signature(source: &str, node: &Node) -> String {
477    let text = node_text(source, node);
478    let first_line = text.lines().next().unwrap_or(text);
479    // Trim trailing opening brace if present
480    let trimmed = first_line.trim_end();
481    let trimmed = trimmed.strip_suffix('{').unwrap_or(trimmed).trim_end();
482    trimmed.to_string()
483}
484
485/// Extract symbols from TypeScript / TSX source.
486fn extract_ts_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
487    let lang = LangId::TypeScript;
488    let capture_names = query.capture_names();
489
490    let export_ranges = collect_export_ranges(source, root, query);
491
492    let mut symbols = Vec::new();
493    let mut cursor = QueryCursor::new();
494    let mut matches = cursor.matches(query, *root, source.as_bytes());
495
496    while let Some(m) = {
497        matches.advance();
498        matches.get()
499    } {
500        // Determine what kind of match this is by looking at capture names
501        let mut fn_name_node = None;
502        let mut fn_def_node = None;
503        let mut arrow_name_node = None;
504        let mut arrow_def_node = None;
505        let mut class_name_node = None;
506        let mut class_def_node = None;
507        let mut method_class_name_node = None;
508        let mut method_name_node = None;
509        let mut method_def_node = None;
510        let mut interface_name_node = None;
511        let mut interface_def_node = None;
512        let mut enum_name_node = None;
513        let mut enum_def_node = None;
514        let mut type_alias_name_node = None;
515        let mut type_alias_def_node = None;
516        let mut var_name_node = None;
517        let mut var_def_node = None;
518
519        for cap in m.captures {
520            let Some(&name) = capture_names.get(cap.index as usize) else {
521                continue;
522            };
523            match name {
524                "fn.name" => fn_name_node = Some(cap.node),
525                "fn.def" => fn_def_node = Some(cap.node),
526                "arrow.name" => arrow_name_node = Some(cap.node),
527                "arrow.def" => arrow_def_node = Some(cap.node),
528                "class.name" => class_name_node = Some(cap.node),
529                "class.def" => class_def_node = Some(cap.node),
530                "method.class_name" => method_class_name_node = Some(cap.node),
531                "method.name" => method_name_node = Some(cap.node),
532                "method.def" => method_def_node = Some(cap.node),
533                "interface.name" => interface_name_node = Some(cap.node),
534                "interface.def" => interface_def_node = Some(cap.node),
535                "enum.name" => enum_name_node = Some(cap.node),
536                "enum.def" => enum_def_node = Some(cap.node),
537                "type_alias.name" => type_alias_name_node = Some(cap.node),
538                "type_alias.def" => type_alias_def_node = Some(cap.node),
539                "var.name" => var_name_node = Some(cap.node),
540                "var.def" => var_def_node = Some(cap.node),
541                // var.value/var.decl removed — not needed
542                _ => {}
543            }
544        }
545
546        // Function declaration
547        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
548            symbols.push(Symbol {
549                name: node_text(source, &name_node).to_string(),
550                kind: SymbolKind::Function,
551                range: node_range_with_decorators(&def_node, source, lang),
552                signature: Some(extract_signature(source, &def_node)),
553                scope_chain: vec![],
554                exported: is_exported(&def_node, &export_ranges),
555                parent: None,
556            });
557        }
558
559        // Arrow function
560        if let (Some(name_node), Some(def_node)) = (arrow_name_node, arrow_def_node) {
561            symbols.push(Symbol {
562                name: node_text(source, &name_node).to_string(),
563                kind: SymbolKind::Function,
564                range: node_range_with_decorators(&def_node, source, lang),
565                signature: Some(extract_signature(source, &def_node)),
566                scope_chain: vec![],
567                exported: is_exported(&def_node, &export_ranges),
568                parent: None,
569            });
570        }
571
572        // Class declaration
573        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
574            symbols.push(Symbol {
575                name: node_text(source, &name_node).to_string(),
576                kind: SymbolKind::Class,
577                range: node_range_with_decorators(&def_node, source, lang),
578                signature: Some(extract_signature(source, &def_node)),
579                scope_chain: vec![],
580                exported: is_exported(&def_node, &export_ranges),
581                parent: None,
582            });
583        }
584
585        // Method definition
586        if let (Some(class_name_node), Some(name_node), Some(def_node)) =
587            (method_class_name_node, method_name_node, method_def_node)
588        {
589            let class_name = node_text(source, &class_name_node).to_string();
590            symbols.push(Symbol {
591                name: node_text(source, &name_node).to_string(),
592                kind: SymbolKind::Method,
593                range: node_range_with_decorators(&def_node, source, lang),
594                signature: Some(extract_signature(source, &def_node)),
595                scope_chain: vec![class_name.clone()],
596                exported: false, // methods inherit export from class
597                parent: Some(class_name),
598            });
599        }
600
601        // Interface declaration
602        if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
603            symbols.push(Symbol {
604                name: node_text(source, &name_node).to_string(),
605                kind: SymbolKind::Interface,
606                range: node_range_with_decorators(&def_node, source, lang),
607                signature: Some(extract_signature(source, &def_node)),
608                scope_chain: vec![],
609                exported: is_exported(&def_node, &export_ranges),
610                parent: None,
611            });
612        }
613
614        // Enum declaration
615        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
616            symbols.push(Symbol {
617                name: node_text(source, &name_node).to_string(),
618                kind: SymbolKind::Enum,
619                range: node_range_with_decorators(&def_node, source, lang),
620                signature: Some(extract_signature(source, &def_node)),
621                scope_chain: vec![],
622                exported: is_exported(&def_node, &export_ranges),
623                parent: None,
624            });
625        }
626
627        // Type alias
628        if let (Some(name_node), Some(def_node)) = (type_alias_name_node, type_alias_def_node) {
629            symbols.push(Symbol {
630                name: node_text(source, &name_node).to_string(),
631                kind: SymbolKind::TypeAlias,
632                range: node_range_with_decorators(&def_node, source, lang),
633                signature: Some(extract_signature(source, &def_node)),
634                scope_chain: vec![],
635                exported: is_exported(&def_node, &export_ranges),
636                parent: None,
637            });
638        }
639
640        // Top-level const/let variable declaration (not arrow functions — those are handled above)
641        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
642            // Only include module-scope variables (parent is program/export_statement, not inside a function)
643            let is_top_level = def_node
644                .parent()
645                .map(|p| p.kind() == "program" || p.kind() == "export_statement")
646                .unwrap_or(false);
647            let is_function_like = lexical_declaration_has_function_value(&def_node);
648            let name = node_text(source, &name_node).to_string();
649            let already_captured = symbols.iter().any(|s| s.name == name);
650            if is_top_level && !is_function_like && !already_captured {
651                symbols.push(Symbol {
652                    name,
653                    kind: SymbolKind::Variable,
654                    range: node_range_with_decorators(&def_node, source, lang),
655                    signature: Some(extract_signature(source, &def_node)),
656                    scope_chain: vec![],
657                    exported: is_exported(&def_node, &export_ranges),
658                    parent: None,
659                });
660            }
661        }
662    }
663
664    // Deduplicate: methods can appear as both class and method captures
665    dedup_symbols(&mut symbols);
666    Ok(symbols)
667}
668
669/// Extract symbols from JavaScript source.
670fn extract_js_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
671    let lang = LangId::JavaScript;
672    let capture_names = query.capture_names();
673
674    let export_ranges = collect_export_ranges(source, root, query);
675
676    let mut symbols = Vec::new();
677    let mut cursor = QueryCursor::new();
678    let mut matches = cursor.matches(query, *root, source.as_bytes());
679
680    while let Some(m) = {
681        matches.advance();
682        matches.get()
683    } {
684        let mut fn_name_node = None;
685        let mut fn_def_node = None;
686        let mut arrow_name_node = None;
687        let mut arrow_def_node = None;
688        let mut class_name_node = None;
689        let mut class_def_node = None;
690        let mut method_class_name_node = None;
691        let mut method_name_node = None;
692        let mut method_def_node = None;
693
694        for cap in m.captures {
695            let Some(&name) = capture_names.get(cap.index as usize) else {
696                continue;
697            };
698            match name {
699                "fn.name" => fn_name_node = Some(cap.node),
700                "fn.def" => fn_def_node = Some(cap.node),
701                "arrow.name" => arrow_name_node = Some(cap.node),
702                "arrow.def" => arrow_def_node = Some(cap.node),
703                "class.name" => class_name_node = Some(cap.node),
704                "class.def" => class_def_node = Some(cap.node),
705                "method.class_name" => method_class_name_node = Some(cap.node),
706                "method.name" => method_name_node = Some(cap.node),
707                "method.def" => method_def_node = Some(cap.node),
708                _ => {}
709            }
710        }
711
712        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
713            symbols.push(Symbol {
714                name: node_text(source, &name_node).to_string(),
715                kind: SymbolKind::Function,
716                range: node_range_with_decorators(&def_node, source, lang),
717                signature: Some(extract_signature(source, &def_node)),
718                scope_chain: vec![],
719                exported: is_exported(&def_node, &export_ranges),
720                parent: None,
721            });
722        }
723
724        if let (Some(name_node), Some(def_node)) = (arrow_name_node, arrow_def_node) {
725            symbols.push(Symbol {
726                name: node_text(source, &name_node).to_string(),
727                kind: SymbolKind::Function,
728                range: node_range_with_decorators(&def_node, source, lang),
729                signature: Some(extract_signature(source, &def_node)),
730                scope_chain: vec![],
731                exported: is_exported(&def_node, &export_ranges),
732                parent: None,
733            });
734        }
735
736        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
737            symbols.push(Symbol {
738                name: node_text(source, &name_node).to_string(),
739                kind: SymbolKind::Class,
740                range: node_range_with_decorators(&def_node, source, lang),
741                signature: Some(extract_signature(source, &def_node)),
742                scope_chain: vec![],
743                exported: is_exported(&def_node, &export_ranges),
744                parent: None,
745            });
746        }
747
748        if let (Some(class_name_node), Some(name_node), Some(def_node)) =
749            (method_class_name_node, method_name_node, method_def_node)
750        {
751            let class_name = node_text(source, &class_name_node).to_string();
752            symbols.push(Symbol {
753                name: node_text(source, &name_node).to_string(),
754                kind: SymbolKind::Method,
755                range: node_range_with_decorators(&def_node, source, lang),
756                signature: Some(extract_signature(source, &def_node)),
757                scope_chain: vec![class_name.clone()],
758                exported: false,
759                parent: Some(class_name),
760            });
761        }
762    }
763
764    dedup_symbols(&mut symbols);
765    Ok(symbols)
766}
767
768/// Walk parent nodes to build a scope chain for Python symbols.
769/// A function inside `class_definition > block` gets the class name in its scope.
770fn py_scope_chain(node: &Node, source: &str) -> Vec<String> {
771    let mut chain = Vec::new();
772    let mut current = node.parent();
773    while let Some(parent) = current {
774        if parent.kind() == "class_definition" {
775            if let Some(name_node) = parent.child_by_field_name("name") {
776                chain.push(node_text(source, &name_node).to_string());
777            }
778        }
779        current = parent.parent();
780    }
781    chain.reverse();
782    chain
783}
784
785/// Extract symbols from Python source.
786fn extract_py_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
787    let lang = LangId::Python;
788    let capture_names = query.capture_names();
789
790    let mut symbols = Vec::new();
791    let mut cursor = QueryCursor::new();
792    let mut matches = cursor.matches(query, *root, source.as_bytes());
793
794    // Track decorated definitions to avoid double-counting
795    let mut decorated_fn_lines = std::collections::HashSet::new();
796
797    // First pass: collect decorated definition info
798    {
799        let mut cursor2 = QueryCursor::new();
800        let mut matches2 = cursor2.matches(query, *root, source.as_bytes());
801        while let Some(m) = {
802            matches2.advance();
803            matches2.get()
804        } {
805            let mut dec_def_node = None;
806            let mut dec_decorator_node = None;
807
808            for cap in m.captures {
809                let Some(&name) = capture_names.get(cap.index as usize) else {
810                    continue;
811                };
812                match name {
813                    "dec.def" => dec_def_node = Some(cap.node),
814                    "dec.decorator" => dec_decorator_node = Some(cap.node),
815                    _ => {}
816                }
817            }
818
819            if let (Some(def_node), Some(_dec_node)) = (dec_def_node, dec_decorator_node) {
820                // Find the inner function_definition or class_definition
821                let mut child_cursor = def_node.walk();
822                if child_cursor.goto_first_child() {
823                    loop {
824                        let child = child_cursor.node();
825                        if child.kind() == "function_definition"
826                            || child.kind() == "class_definition"
827                        {
828                            decorated_fn_lines.insert(child.start_position().row);
829                        }
830                        if !child_cursor.goto_next_sibling() {
831                            break;
832                        }
833                    }
834                }
835            }
836        }
837    }
838
839    while let Some(m) = {
840        matches.advance();
841        matches.get()
842    } {
843        let mut fn_name_node = None;
844        let mut fn_def_node = None;
845        let mut class_name_node = None;
846        let mut class_def_node = None;
847
848        for cap in m.captures {
849            let Some(&name) = capture_names.get(cap.index as usize) else {
850                continue;
851            };
852            match name {
853                "fn.name" => fn_name_node = Some(cap.node),
854                "fn.def" => fn_def_node = Some(cap.node),
855                "class.name" => class_name_node = Some(cap.node),
856                "class.def" => class_def_node = Some(cap.node),
857                _ => {}
858            }
859        }
860
861        // Function definition
862        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
863            let scope = py_scope_chain(&def_node, source);
864            let is_method = !scope.is_empty();
865            let name = node_text(source, &name_node).to_string();
866            // Skip __init__ and other dunders as separate symbols — they're methods
867            let kind = if is_method {
868                SymbolKind::Method
869            } else {
870                SymbolKind::Function
871            };
872
873            // Build signature — include decorator if this is a decorated function
874            let sig = if decorated_fn_lines.contains(&def_node.start_position().row) {
875                // Find the decorated_definition parent to get decorator text
876                let mut sig_parts = Vec::new();
877                let mut parent = def_node.parent();
878                while let Some(p) = parent {
879                    if p.kind() == "decorated_definition" {
880                        // Get decorator lines
881                        let mut dc = p.walk();
882                        if dc.goto_first_child() {
883                            loop {
884                                if dc.node().kind() == "decorator" {
885                                    sig_parts.push(node_text(source, &dc.node()).to_string());
886                                }
887                                if !dc.goto_next_sibling() {
888                                    break;
889                                }
890                            }
891                        }
892                        break;
893                    }
894                    parent = p.parent();
895                }
896                sig_parts.push(extract_signature(source, &def_node));
897                Some(sig_parts.join("\n"))
898            } else {
899                Some(extract_signature(source, &def_node))
900            };
901
902            symbols.push(Symbol {
903                name,
904                kind,
905                range: node_range_with_decorators(&def_node, source, lang),
906                signature: sig,
907                scope_chain: scope.clone(),
908                exported: false, // Python has no export concept
909                parent: scope.last().cloned(),
910            });
911        }
912
913        // Class definition
914        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
915            let scope = py_scope_chain(&def_node, source);
916
917            // Build signature — include decorator if decorated
918            let sig = if decorated_fn_lines.contains(&def_node.start_position().row) {
919                let mut sig_parts = Vec::new();
920                let mut parent = def_node.parent();
921                while let Some(p) = parent {
922                    if p.kind() == "decorated_definition" {
923                        let mut dc = p.walk();
924                        if dc.goto_first_child() {
925                            loop {
926                                if dc.node().kind() == "decorator" {
927                                    sig_parts.push(node_text(source, &dc.node()).to_string());
928                                }
929                                if !dc.goto_next_sibling() {
930                                    break;
931                                }
932                            }
933                        }
934                        break;
935                    }
936                    parent = p.parent();
937                }
938                sig_parts.push(extract_signature(source, &def_node));
939                Some(sig_parts.join("\n"))
940            } else {
941                Some(extract_signature(source, &def_node))
942            };
943
944            symbols.push(Symbol {
945                name: node_text(source, &name_node).to_string(),
946                kind: SymbolKind::Class,
947                range: node_range_with_decorators(&def_node, source, lang),
948                signature: sig,
949                scope_chain: scope.clone(),
950                exported: false,
951                parent: scope.last().cloned(),
952            });
953        }
954    }
955
956    dedup_symbols(&mut symbols);
957    Ok(symbols)
958}
959
960/// Extract symbols from Rust source.
961/// Handles: free functions, struct, enum, trait (as Interface), impl methods with scope chains.
962fn extract_rs_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
963    let lang = LangId::Rust;
964    let capture_names = query.capture_names();
965
966    // Collect all visibility_modifier byte ranges first
967    let mut vis_ranges: Vec<std::ops::Range<usize>> = Vec::new();
968    {
969        let vis_idx = capture_names.iter().position(|n| *n == "vis.mod");
970        if let Some(idx) = vis_idx {
971            let idx = idx as u32;
972            let mut cursor = QueryCursor::new();
973            let mut matches = cursor.matches(query, *root, source.as_bytes());
974            while let Some(m) = {
975                matches.advance();
976                matches.get()
977            } {
978                for cap in m.captures {
979                    if cap.index == idx {
980                        vis_ranges.push(cap.node.byte_range());
981                    }
982                }
983            }
984        }
985    }
986
987    let is_pub = |node: &Node| -> bool {
988        // Check if the node has a visibility_modifier as a direct child
989        let mut child_cursor = node.walk();
990        if child_cursor.goto_first_child() {
991            loop {
992                if child_cursor.node().kind() == "visibility_modifier" {
993                    return true;
994                }
995                if !child_cursor.goto_next_sibling() {
996                    break;
997                }
998            }
999        }
1000        false
1001    };
1002
1003    let mut symbols = Vec::new();
1004    let mut cursor = QueryCursor::new();
1005    let mut matches = cursor.matches(query, *root, source.as_bytes());
1006
1007    while let Some(m) = {
1008        matches.advance();
1009        matches.get()
1010    } {
1011        let mut fn_name_node = None;
1012        let mut fn_def_node = None;
1013        let mut struct_name_node = None;
1014        let mut struct_def_node = None;
1015        let mut enum_name_node = None;
1016        let mut enum_def_node = None;
1017        let mut trait_name_node = None;
1018        let mut trait_def_node = None;
1019        let mut impl_def_node = None;
1020
1021        for cap in m.captures {
1022            let Some(&name) = capture_names.get(cap.index as usize) else {
1023                continue;
1024            };
1025            match name {
1026                "fn.name" => fn_name_node = Some(cap.node),
1027                "fn.def" => fn_def_node = Some(cap.node),
1028                "struct.name" => struct_name_node = Some(cap.node),
1029                "struct.def" => struct_def_node = Some(cap.node),
1030                "enum.name" => enum_name_node = Some(cap.node),
1031                "enum.def" => enum_def_node = Some(cap.node),
1032                "trait.name" => trait_name_node = Some(cap.node),
1033                "trait.def" => trait_def_node = Some(cap.node),
1034                "impl.def" => impl_def_node = Some(cap.node),
1035                _ => {}
1036            }
1037        }
1038
1039        // Free function (not inside impl block — check parent)
1040        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1041            let parent = def_node.parent();
1042            let in_impl = parent
1043                .map(|p| p.kind() == "declaration_list")
1044                .unwrap_or(false);
1045            if !in_impl {
1046                symbols.push(Symbol {
1047                    name: node_text(source, &name_node).to_string(),
1048                    kind: SymbolKind::Function,
1049                    range: node_range_with_decorators(&def_node, source, lang),
1050                    signature: Some(extract_signature(source, &def_node)),
1051                    scope_chain: vec![],
1052                    exported: is_pub(&def_node),
1053                    parent: None,
1054                });
1055            }
1056        }
1057
1058        // Struct
1059        if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
1060            symbols.push(Symbol {
1061                name: node_text(source, &name_node).to_string(),
1062                kind: SymbolKind::Struct,
1063                range: node_range_with_decorators(&def_node, source, lang),
1064                signature: Some(extract_signature(source, &def_node)),
1065                scope_chain: vec![],
1066                exported: is_pub(&def_node),
1067                parent: None,
1068            });
1069        }
1070
1071        // Enum
1072        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
1073            symbols.push(Symbol {
1074                name: node_text(source, &name_node).to_string(),
1075                kind: SymbolKind::Enum,
1076                range: node_range_with_decorators(&def_node, source, lang),
1077                signature: Some(extract_signature(source, &def_node)),
1078                scope_chain: vec![],
1079                exported: is_pub(&def_node),
1080                parent: None,
1081            });
1082        }
1083
1084        // Trait (mapped to Interface kind)
1085        if let (Some(name_node), Some(def_node)) = (trait_name_node, trait_def_node) {
1086            symbols.push(Symbol {
1087                name: node_text(source, &name_node).to_string(),
1088                kind: SymbolKind::Interface,
1089                range: node_range_with_decorators(&def_node, source, lang),
1090                signature: Some(extract_signature(source, &def_node)),
1091                scope_chain: vec![],
1092                exported: is_pub(&def_node),
1093                parent: None,
1094            });
1095        }
1096
1097        // Impl block — extract methods from inside
1098        if let Some(impl_node) = impl_def_node {
1099            // Find the type name(s) from the impl
1100            // `impl TypeName { ... }` → scope = ["TypeName"]
1101            // `impl Trait for TypeName { ... }` → scope = ["Trait for TypeName"]
1102            let mut type_names: Vec<String> = Vec::new();
1103            let mut child_cursor = impl_node.walk();
1104            if child_cursor.goto_first_child() {
1105                loop {
1106                    let child = child_cursor.node();
1107                    if child.kind() == "type_identifier" || child.kind() == "generic_type" {
1108                        type_names.push(node_text(source, &child).to_string());
1109                    }
1110                    if !child_cursor.goto_next_sibling() {
1111                        break;
1112                    }
1113                }
1114            }
1115
1116            let scope_name = if type_names.len() >= 2 {
1117                // impl Trait for Type
1118                format!("{} for {}", type_names[0], type_names[1])
1119            } else if type_names.len() == 1 {
1120                type_names[0].clone()
1121            } else {
1122                String::new()
1123            };
1124
1125            let parent_name = type_names.last().cloned().unwrap_or_default();
1126
1127            // Find declaration_list and extract function_items
1128            let mut child_cursor = impl_node.walk();
1129            if child_cursor.goto_first_child() {
1130                loop {
1131                    let child = child_cursor.node();
1132                    if child.kind() == "declaration_list" {
1133                        let mut fn_cursor = child.walk();
1134                        if fn_cursor.goto_first_child() {
1135                            loop {
1136                                let fn_node = fn_cursor.node();
1137                                if fn_node.kind() == "function_item" {
1138                                    if let Some(name_node) = fn_node.child_by_field_name("name") {
1139                                        symbols.push(Symbol {
1140                                            name: node_text(source, &name_node).to_string(),
1141                                            kind: SymbolKind::Method,
1142                                            range: node_range_with_decorators(
1143                                                &fn_node, source, lang,
1144                                            ),
1145                                            signature: Some(extract_signature(source, &fn_node)),
1146                                            scope_chain: if scope_name.is_empty() {
1147                                                vec![]
1148                                            } else {
1149                                                vec![scope_name.clone()]
1150                                            },
1151                                            exported: is_pub(&fn_node),
1152                                            parent: if parent_name.is_empty() {
1153                                                None
1154                                            } else {
1155                                                Some(parent_name.clone())
1156                                            },
1157                                        });
1158                                    }
1159                                }
1160                                if !fn_cursor.goto_next_sibling() {
1161                                    break;
1162                                }
1163                            }
1164                        }
1165                    }
1166                    if !child_cursor.goto_next_sibling() {
1167                        break;
1168                    }
1169                }
1170            }
1171        }
1172    }
1173
1174    dedup_symbols(&mut symbols);
1175    Ok(symbols)
1176}
1177
1178/// Extract symbols from Go source.
1179/// Handles: functions, methods (with receiver scope chain), struct/interface types,
1180/// uppercase-first-letter export detection.
1181fn extract_go_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1182    let lang = LangId::Go;
1183    let capture_names = query.capture_names();
1184
1185    let is_go_exported = |name: &str| -> bool {
1186        name.chars()
1187            .next()
1188            .map(|c| c.is_uppercase())
1189            .unwrap_or(false)
1190    };
1191
1192    let mut symbols = Vec::new();
1193    let mut cursor = QueryCursor::new();
1194    let mut matches = cursor.matches(query, *root, source.as_bytes());
1195
1196    while let Some(m) = {
1197        matches.advance();
1198        matches.get()
1199    } {
1200        let mut fn_name_node = None;
1201        let mut fn_def_node = None;
1202        let mut method_name_node = None;
1203        let mut method_def_node = None;
1204        let mut type_name_node = None;
1205        let mut type_body_node = None;
1206        let mut type_def_node = None;
1207
1208        for cap in m.captures {
1209            let Some(&name) = capture_names.get(cap.index as usize) else {
1210                continue;
1211            };
1212            match name {
1213                "fn.name" => fn_name_node = Some(cap.node),
1214                "fn.def" => fn_def_node = Some(cap.node),
1215                "method.name" => method_name_node = Some(cap.node),
1216                "method.def" => method_def_node = Some(cap.node),
1217                "type.name" => type_name_node = Some(cap.node),
1218                "type.body" => type_body_node = Some(cap.node),
1219                "type.def" => type_def_node = Some(cap.node),
1220                _ => {}
1221            }
1222        }
1223
1224        // Function declaration
1225        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1226            let name = node_text(source, &name_node).to_string();
1227            symbols.push(Symbol {
1228                exported: is_go_exported(&name),
1229                name,
1230                kind: SymbolKind::Function,
1231                range: node_range_with_decorators(&def_node, source, lang),
1232                signature: Some(extract_signature(source, &def_node)),
1233                scope_chain: vec![],
1234                parent: None,
1235            });
1236        }
1237
1238        // Method declaration (with receiver)
1239        if let (Some(name_node), Some(def_node)) = (method_name_node, method_def_node) {
1240            let name = node_text(source, &name_node).to_string();
1241
1242            // Extract receiver type from the first parameter_list
1243            let receiver_type = extract_go_receiver_type(&def_node, source);
1244            let scope_chain = if let Some(ref rt) = receiver_type {
1245                vec![rt.clone()]
1246            } else {
1247                vec![]
1248            };
1249
1250            symbols.push(Symbol {
1251                exported: is_go_exported(&name),
1252                name,
1253                kind: SymbolKind::Method,
1254                range: node_range_with_decorators(&def_node, source, lang),
1255                signature: Some(extract_signature(source, &def_node)),
1256                scope_chain,
1257                parent: receiver_type,
1258            });
1259        }
1260
1261        // Type declarations (struct or interface)
1262        if let (Some(name_node), Some(body_node), Some(def_node)) =
1263            (type_name_node, type_body_node, type_def_node)
1264        {
1265            let name = node_text(source, &name_node).to_string();
1266            let kind = match body_node.kind() {
1267                "struct_type" => SymbolKind::Struct,
1268                "interface_type" => SymbolKind::Interface,
1269                _ => SymbolKind::TypeAlias,
1270            };
1271
1272            symbols.push(Symbol {
1273                exported: is_go_exported(&name),
1274                name,
1275                kind,
1276                range: node_range_with_decorators(&def_node, source, lang),
1277                signature: Some(extract_signature(source, &def_node)),
1278                scope_chain: vec![],
1279                parent: None,
1280            });
1281        }
1282    }
1283
1284    dedup_symbols(&mut symbols);
1285    Ok(symbols)
1286}
1287
1288/// Extract the receiver type from a Go method_declaration node.
1289/// e.g. `func (m *MyStruct) String()` → Some("MyStruct")
1290fn extract_go_receiver_type(method_node: &Node, source: &str) -> Option<String> {
1291    // The first parameter_list is the receiver
1292    let mut child_cursor = method_node.walk();
1293    if child_cursor.goto_first_child() {
1294        loop {
1295            let child = child_cursor.node();
1296            if child.kind() == "parameter_list" {
1297                // Walk into parameter_list to find type_identifier
1298                return find_type_identifier_recursive(&child, source);
1299            }
1300            if !child_cursor.goto_next_sibling() {
1301                break;
1302            }
1303        }
1304    }
1305    None
1306}
1307
1308/// Recursively find the first type_identifier node in a subtree.
1309fn find_type_identifier_recursive(node: &Node, source: &str) -> Option<String> {
1310    if node.kind() == "type_identifier" {
1311        return Some(node_text(source, node).to_string());
1312    }
1313    let mut cursor = node.walk();
1314    if cursor.goto_first_child() {
1315        loop {
1316            if let Some(result) = find_type_identifier_recursive(&cursor.node(), source) {
1317                return Some(result);
1318            }
1319            if !cursor.goto_next_sibling() {
1320                break;
1321            }
1322        }
1323    }
1324    None
1325}
1326
1327/// Extract markdown headings as symbols.
1328/// Each heading becomes a symbol with kind `Heading`, and its range covers the entire
1329/// section (from the heading to the next heading at the same or higher level, or EOF).
1330fn extract_md_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
1331    let mut symbols = Vec::new();
1332    extract_md_sections(source, root, &mut symbols, &[]);
1333    Ok(symbols)
1334}
1335
1336/// Recursively walk `section` nodes to build the heading hierarchy.
1337fn extract_md_sections(
1338    source: &str,
1339    node: &Node,
1340    symbols: &mut Vec<Symbol>,
1341    scope_chain: &[String],
1342) {
1343    let mut cursor = node.walk();
1344    if !cursor.goto_first_child() {
1345        return;
1346    }
1347
1348    loop {
1349        let child = cursor.node();
1350        match child.kind() {
1351            "section" => {
1352                // A section contains an atx_heading as its first child,
1353                // followed by content and possibly nested sections.
1354                let mut section_cursor = child.walk();
1355                let mut heading_name = String::new();
1356                let mut heading_level: u8 = 0;
1357
1358                if section_cursor.goto_first_child() {
1359                    loop {
1360                        let section_child = section_cursor.node();
1361                        if section_child.kind() == "atx_heading" {
1362                            // Extract heading level from marker type
1363                            let mut h_cursor = section_child.walk();
1364                            if h_cursor.goto_first_child() {
1365                                loop {
1366                                    let h_child = h_cursor.node();
1367                                    let kind = h_child.kind();
1368                                    if kind.starts_with("atx_h") && kind.ends_with("_marker") {
1369                                        // "atx_h1_marker" → level 1, "atx_h2_marker" → level 2, etc.
1370                                        heading_level = kind
1371                                            .strip_prefix("atx_h")
1372                                            .and_then(|s| s.strip_suffix("_marker"))
1373                                            .and_then(|s| s.parse::<u8>().ok())
1374                                            .unwrap_or(1);
1375                                    } else if h_child.kind() == "inline" {
1376                                        heading_name =
1377                                            node_text(source, &h_child).trim().to_string();
1378                                    }
1379                                    if !h_cursor.goto_next_sibling() {
1380                                        break;
1381                                    }
1382                                }
1383                            }
1384                        }
1385                        if !section_cursor.goto_next_sibling() {
1386                            break;
1387                        }
1388                    }
1389                }
1390
1391                if !heading_name.is_empty() {
1392                    let range = node_range(&child);
1393                    let signature = format!(
1394                        "{} {}",
1395                        "#".repeat((heading_level as usize).min(6)),
1396                        heading_name
1397                    );
1398
1399                    symbols.push(Symbol {
1400                        name: heading_name.clone(),
1401                        kind: SymbolKind::Heading,
1402                        range,
1403                        signature: Some(signature),
1404                        scope_chain: scope_chain.to_vec(),
1405                        exported: false,
1406                        parent: scope_chain.last().cloned(),
1407                    });
1408
1409                    // Recurse into the section for nested headings
1410                    let mut new_scope = scope_chain.to_vec();
1411                    new_scope.push(heading_name);
1412                    extract_md_sections(source, &child, symbols, &new_scope);
1413                }
1414            }
1415            _ => {}
1416        }
1417
1418        if !cursor.goto_next_sibling() {
1419            break;
1420        }
1421    }
1422}
1423
1424/// Remove duplicate symbols based on (name, kind, start_line).
1425/// Class declarations can match both "class" and "method" patterns,
1426/// producing duplicates.
1427fn dedup_symbols(symbols: &mut Vec<Symbol>) {
1428    let mut seen = std::collections::HashSet::new();
1429    symbols.retain(|s| {
1430        let key = (s.name.clone(), format!("{:?}", s.kind), s.range.start_line);
1431        seen.insert(key)
1432    });
1433}
1434
1435/// Provider that uses tree-sitter for real symbol extraction.
1436/// Implements the `LanguageProvider` trait from `language.rs`.
1437pub struct TreeSitterProvider {
1438    parser: RefCell<FileParser>,
1439}
1440
1441#[derive(Debug, Clone)]
1442struct ReExportTarget {
1443    file: PathBuf,
1444    symbol_name: String,
1445}
1446
1447impl TreeSitterProvider {
1448    /// Create a new `TreeSitterProvider` backed by a fresh `FileParser`.
1449    pub fn new() -> Self {
1450        Self {
1451            parser: RefCell::new(FileParser::new()),
1452        }
1453    }
1454
1455    fn resolve_symbol_inner(
1456        &self,
1457        file: &Path,
1458        name: &str,
1459        depth: usize,
1460        visited: &mut HashSet<(PathBuf, String)>,
1461    ) -> Result<Vec<SymbolMatch>, AftError> {
1462        if depth > MAX_REEXPORT_DEPTH {
1463            return Ok(Vec::new());
1464        }
1465
1466        let canonical_file = std::fs::canonicalize(file).unwrap_or_else(|_| file.to_path_buf());
1467        if !visited.insert((canonical_file, name.to_string())) {
1468            return Ok(Vec::new());
1469        }
1470
1471        let symbols = self.parser.borrow_mut().extract_symbols(file)?;
1472        let local_matches = symbol_matches_in_file(file, &symbols, name);
1473        if !local_matches.is_empty() {
1474            return Ok(local_matches);
1475        }
1476
1477        if name == "default" {
1478            let default_matches = self.resolve_local_default_export(file, &symbols)?;
1479            if !default_matches.is_empty() {
1480                return Ok(default_matches);
1481            }
1482        }
1483
1484        let reexport_targets = self.collect_reexport_targets(file, name)?;
1485        let mut matches = Vec::new();
1486        let mut seen = HashSet::new();
1487        for target in reexport_targets {
1488            for resolved in
1489                self.resolve_symbol_inner(&target.file, &target.symbol_name, depth + 1, visited)?
1490            {
1491                let key = format!(
1492                    "{}:{}:{}:{}:{}:{}",
1493                    resolved.file,
1494                    resolved.symbol.name,
1495                    resolved.symbol.range.start_line,
1496                    resolved.symbol.range.start_col,
1497                    resolved.symbol.range.end_line,
1498                    resolved.symbol.range.end_col
1499                );
1500                if seen.insert(key) {
1501                    matches.push(resolved);
1502                }
1503            }
1504        }
1505
1506        Ok(matches)
1507    }
1508
1509    fn collect_reexport_targets(
1510        &self,
1511        file: &Path,
1512        requested_name: &str,
1513    ) -> Result<Vec<ReExportTarget>, AftError> {
1514        let (source, tree, lang) = self.read_parsed_file(file)?;
1515        if !matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
1516            return Ok(Vec::new());
1517        }
1518
1519        let mut targets = Vec::new();
1520        let root = tree.root_node();
1521        let from_dir = file.parent().unwrap_or_else(|| Path::new("."));
1522
1523        let mut cursor = root.walk();
1524        if !cursor.goto_first_child() {
1525            return Ok(targets);
1526        }
1527
1528        loop {
1529            let node = cursor.node();
1530            if node.kind() == "export_statement" {
1531                let Some(source_node) = node.child_by_field_name("source") else {
1532                    if !cursor.goto_next_sibling() {
1533                        break;
1534                    }
1535                    continue;
1536                };
1537
1538                let Some(module_path) = string_content(&source, &source_node) else {
1539                    if !cursor.goto_next_sibling() {
1540                        break;
1541                    }
1542                    continue;
1543                };
1544
1545                let Some(target_file) = resolve_module_path(from_dir, &module_path) else {
1546                    if !cursor.goto_next_sibling() {
1547                        break;
1548                    }
1549                    continue;
1550                };
1551
1552                if let Some(export_clause) = find_child_by_kind(node, "export_clause") {
1553                    if let Some(symbol_name) =
1554                        resolve_export_clause_name(&source, &export_clause, requested_name)
1555                    {
1556                        targets.push(ReExportTarget {
1557                            file: target_file,
1558                            symbol_name,
1559                        });
1560                    }
1561                } else if export_statement_has_wildcard(&source, &node) {
1562                    targets.push(ReExportTarget {
1563                        file: target_file,
1564                        symbol_name: requested_name.to_string(),
1565                    });
1566                }
1567            }
1568
1569            if !cursor.goto_next_sibling() {
1570                break;
1571            }
1572        }
1573
1574        Ok(targets)
1575    }
1576
1577    fn resolve_local_default_export(
1578        &self,
1579        file: &Path,
1580        symbols: &[Symbol],
1581    ) -> Result<Vec<SymbolMatch>, AftError> {
1582        let (source, tree, lang) = self.read_parsed_file(file)?;
1583        if !matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
1584            return Ok(Vec::new());
1585        }
1586
1587        let root = tree.root_node();
1588        let mut matches = Vec::new();
1589        let mut seen = HashSet::new();
1590
1591        let mut cursor = root.walk();
1592        if !cursor.goto_first_child() {
1593            return Ok(matches);
1594        }
1595
1596        loop {
1597            let node = cursor.node();
1598            if node.kind() == "export_statement"
1599                && node.child_by_field_name("source").is_none()
1600                && node_contains_token(&source, &node, "default")
1601            {
1602                if let Some(target_name) = default_export_target_name(&source, &node) {
1603                    for symbol_match in symbol_matches_in_file(file, symbols, &target_name) {
1604                        let key = format!(
1605                            "{}:{}:{}:{}:{}:{}",
1606                            symbol_match.file,
1607                            symbol_match.symbol.name,
1608                            symbol_match.symbol.range.start_line,
1609                            symbol_match.symbol.range.start_col,
1610                            symbol_match.symbol.range.end_line,
1611                            symbol_match.symbol.range.end_col
1612                        );
1613                        if seen.insert(key) {
1614                            matches.push(symbol_match);
1615                        }
1616                    }
1617                }
1618            }
1619
1620            if !cursor.goto_next_sibling() {
1621                break;
1622            }
1623        }
1624
1625        Ok(matches)
1626    }
1627
1628    fn read_parsed_file(&self, file: &Path) -> Result<(String, Tree, LangId), AftError> {
1629        let source = std::fs::read_to_string(file).map_err(|e| AftError::FileNotFound {
1630            path: format!("{}: {}", file.display(), e),
1631        })?;
1632        let (tree, lang) = {
1633            let mut parser = self.parser.borrow_mut();
1634            parser.parse_cloned(file)?
1635        };
1636        Ok((source, tree, lang))
1637    }
1638}
1639
1640fn symbol_matches_in_file(file: &Path, symbols: &[Symbol], name: &str) -> Vec<SymbolMatch> {
1641    symbols
1642        .iter()
1643        .filter(|symbol| symbol.name == name)
1644        .cloned()
1645        .map(|symbol| SymbolMatch {
1646            file: file.display().to_string(),
1647            symbol,
1648        })
1649        .collect()
1650}
1651
1652fn string_content(source: &str, node: &Node) -> Option<String> {
1653    let text = node_text(source, node);
1654    if text.len() < 2 {
1655        return None;
1656    }
1657
1658    Some(
1659        text.trim_start_matches(|c| c == '\'' || c == '"')
1660            .trim_end_matches(|c| c == '\'' || c == '"')
1661            .to_string(),
1662    )
1663}
1664
1665fn find_child_by_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
1666    let mut cursor = node.walk();
1667    if !cursor.goto_first_child() {
1668        return None;
1669    }
1670
1671    loop {
1672        let child = cursor.node();
1673        if child.kind() == kind {
1674            return Some(child);
1675        }
1676        if !cursor.goto_next_sibling() {
1677            break;
1678        }
1679    }
1680
1681    None
1682}
1683
1684fn resolve_export_clause_name(
1685    source: &str,
1686    export_clause: &Node,
1687    requested_name: &str,
1688) -> Option<String> {
1689    let mut cursor = export_clause.walk();
1690    if !cursor.goto_first_child() {
1691        return None;
1692    }
1693
1694    loop {
1695        let child = cursor.node();
1696        if child.kind() == "export_specifier" {
1697            let (source_name, exported_name) = export_specifier_names(source, &child)?;
1698            if exported_name == requested_name {
1699                return Some(source_name);
1700            }
1701        }
1702
1703        if !cursor.goto_next_sibling() {
1704            break;
1705        }
1706    }
1707
1708    None
1709}
1710
1711fn export_specifier_names(source: &str, specifier: &Node) -> Option<(String, String)> {
1712    let source_name = specifier
1713        .child_by_field_name("name")
1714        .map(|node| node_text(source, &node).to_string());
1715    let alias_name = specifier
1716        .child_by_field_name("alias")
1717        .map(|node| node_text(source, &node).to_string());
1718
1719    if let Some(source_name) = source_name {
1720        let exported_name = alias_name.unwrap_or_else(|| source_name.clone());
1721        return Some((source_name, exported_name));
1722    }
1723
1724    let mut names = Vec::new();
1725    let mut cursor = specifier.walk();
1726    if cursor.goto_first_child() {
1727        loop {
1728            let child = cursor.node();
1729            let child_text = node_text(source, &child).trim();
1730            if matches!(
1731                child.kind(),
1732                "identifier" | "type_identifier" | "property_identifier"
1733            ) || child_text == "default"
1734            {
1735                names.push(child_text.to_string());
1736            }
1737            if !cursor.goto_next_sibling() {
1738                break;
1739            }
1740        }
1741    }
1742
1743    match names.as_slice() {
1744        [name] => Some((name.clone(), name.clone())),
1745        [source_name, exported_name, ..] => Some((source_name.clone(), exported_name.clone())),
1746        _ => None,
1747    }
1748}
1749
1750fn export_statement_has_wildcard(source: &str, node: &Node) -> bool {
1751    let mut cursor = node.walk();
1752    if !cursor.goto_first_child() {
1753        return false;
1754    }
1755
1756    loop {
1757        if node_text(source, &cursor.node()).trim() == "*" {
1758            return true;
1759        }
1760        if !cursor.goto_next_sibling() {
1761            break;
1762        }
1763    }
1764
1765    false
1766}
1767
1768fn node_contains_token(source: &str, node: &Node, token: &str) -> bool {
1769    let mut cursor = node.walk();
1770    if !cursor.goto_first_child() {
1771        return false;
1772    }
1773
1774    loop {
1775        if node_text(source, &cursor.node()).trim() == token {
1776            return true;
1777        }
1778        if !cursor.goto_next_sibling() {
1779            break;
1780        }
1781    }
1782
1783    false
1784}
1785
1786fn default_export_target_name(source: &str, export_stmt: &Node) -> Option<String> {
1787    let mut cursor = export_stmt.walk();
1788    if !cursor.goto_first_child() {
1789        return None;
1790    }
1791
1792    loop {
1793        let child = cursor.node();
1794        match child.kind() {
1795            "function_declaration"
1796            | "class_declaration"
1797            | "interface_declaration"
1798            | "enum_declaration"
1799            | "type_alias_declaration"
1800            | "lexical_declaration" => {
1801                if let Some(name_node) = child.child_by_field_name("name") {
1802                    return Some(node_text(source, &name_node).to_string());
1803                }
1804
1805                if child.kind() == "lexical_declaration" {
1806                    let mut child_cursor = child.walk();
1807                    if child_cursor.goto_first_child() {
1808                        loop {
1809                            let nested = child_cursor.node();
1810                            if nested.kind() == "variable_declarator" {
1811                                if let Some(name_node) = nested.child_by_field_name("name") {
1812                                    return Some(node_text(source, &name_node).to_string());
1813                                }
1814                            }
1815                            if !child_cursor.goto_next_sibling() {
1816                                break;
1817                            }
1818                        }
1819                    }
1820                }
1821            }
1822            "identifier" | "type_identifier" => {
1823                let text = node_text(source, &child);
1824                if text != "export" && text != "default" {
1825                    return Some(text.to_string());
1826                }
1827            }
1828            _ => {}
1829        }
1830
1831        if !cursor.goto_next_sibling() {
1832            break;
1833        }
1834    }
1835
1836    None
1837}
1838
1839impl crate::language::LanguageProvider for TreeSitterProvider {
1840    fn resolve_symbol(&self, file: &Path, name: &str) -> Result<Vec<SymbolMatch>, AftError> {
1841        let matches = self.resolve_symbol_inner(file, name, 0, &mut HashSet::new())?;
1842
1843        if matches.is_empty() {
1844            Err(AftError::SymbolNotFound {
1845                name: name.to_string(),
1846                file: file.display().to_string(),
1847            })
1848        } else {
1849            Ok(matches)
1850        }
1851    }
1852
1853    fn list_symbols(&self, file: &Path) -> Result<Vec<Symbol>, AftError> {
1854        self.parser.borrow_mut().extract_symbols(file)
1855    }
1856}
1857
1858#[cfg(test)]
1859mod tests {
1860    use super::*;
1861    use crate::language::LanguageProvider;
1862    use std::path::PathBuf;
1863
1864    fn fixture_path(name: &str) -> PathBuf {
1865        PathBuf::from(env!("CARGO_MANIFEST_DIR"))
1866            .join("tests")
1867            .join("fixtures")
1868            .join(name)
1869    }
1870
1871    // --- Language detection ---
1872
1873    #[test]
1874    fn detect_ts() {
1875        assert_eq!(
1876            detect_language(Path::new("foo.ts")),
1877            Some(LangId::TypeScript)
1878        );
1879    }
1880
1881    #[test]
1882    fn detect_tsx() {
1883        assert_eq!(detect_language(Path::new("foo.tsx")), Some(LangId::Tsx));
1884    }
1885
1886    #[test]
1887    fn detect_js() {
1888        assert_eq!(
1889            detect_language(Path::new("foo.js")),
1890            Some(LangId::JavaScript)
1891        );
1892    }
1893
1894    #[test]
1895    fn detect_jsx() {
1896        assert_eq!(
1897            detect_language(Path::new("foo.jsx")),
1898            Some(LangId::JavaScript)
1899        );
1900    }
1901
1902    #[test]
1903    fn detect_py() {
1904        assert_eq!(detect_language(Path::new("foo.py")), Some(LangId::Python));
1905    }
1906
1907    #[test]
1908    fn detect_rs() {
1909        assert_eq!(detect_language(Path::new("foo.rs")), Some(LangId::Rust));
1910    }
1911
1912    #[test]
1913    fn detect_go() {
1914        assert_eq!(detect_language(Path::new("foo.go")), Some(LangId::Go));
1915    }
1916
1917    #[test]
1918    fn detect_unknown_returns_none() {
1919        assert_eq!(detect_language(Path::new("foo.txt")), None);
1920    }
1921
1922    // --- Unsupported extension error ---
1923
1924    #[test]
1925    fn unsupported_extension_returns_invalid_request() {
1926        // Use a file that exists but has an unsupported extension
1927        let path = fixture_path("sample.ts");
1928        let bad_path = path.with_extension("txt");
1929        // Create a dummy file so the error comes from language detection, not I/O
1930        std::fs::write(&bad_path, "hello").unwrap();
1931        let provider = TreeSitterProvider::new();
1932        let result = provider.list_symbols(&bad_path);
1933        std::fs::remove_file(&bad_path).ok();
1934        match result {
1935            Err(AftError::InvalidRequest { message }) => {
1936                assert!(
1937                    message.contains("unsupported file extension"),
1938                    "msg: {}",
1939                    message
1940                );
1941                assert!(message.contains("txt"), "msg: {}", message);
1942            }
1943            other => panic!("expected InvalidRequest, got {:?}", other),
1944        }
1945    }
1946
1947    // --- TypeScript extraction ---
1948
1949    #[test]
1950    fn ts_extracts_all_symbol_kinds() {
1951        let provider = TreeSitterProvider::new();
1952        let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
1953
1954        let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
1955        assert!(
1956            names.contains(&"greet"),
1957            "missing function greet: {:?}",
1958            names
1959        );
1960        assert!(names.contains(&"add"), "missing arrow fn add: {:?}", names);
1961        assert!(
1962            names.contains(&"UserService"),
1963            "missing class UserService: {:?}",
1964            names
1965        );
1966        assert!(
1967            names.contains(&"Config"),
1968            "missing interface Config: {:?}",
1969            names
1970        );
1971        assert!(
1972            names.contains(&"Status"),
1973            "missing enum Status: {:?}",
1974            names
1975        );
1976        assert!(
1977            names.contains(&"UserId"),
1978            "missing type alias UserId: {:?}",
1979            names
1980        );
1981        assert!(
1982            names.contains(&"internalHelper"),
1983            "missing non-exported fn: {:?}",
1984            names
1985        );
1986
1987        // At least 6 unique symbols as required
1988        assert!(
1989            symbols.len() >= 6,
1990            "expected ≥6 symbols, got {}: {:?}",
1991            symbols.len(),
1992            names
1993        );
1994    }
1995
1996    #[test]
1997    fn ts_symbol_kinds_correct() {
1998        let provider = TreeSitterProvider::new();
1999        let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
2000
2001        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
2002
2003        assert_eq!(find("greet").kind, SymbolKind::Function);
2004        assert_eq!(find("add").kind, SymbolKind::Function); // arrow fn → Function
2005        assert_eq!(find("UserService").kind, SymbolKind::Class);
2006        assert_eq!(find("Config").kind, SymbolKind::Interface);
2007        assert_eq!(find("Status").kind, SymbolKind::Enum);
2008        assert_eq!(find("UserId").kind, SymbolKind::TypeAlias);
2009    }
2010
2011    #[test]
2012    fn ts_export_detection() {
2013        let provider = TreeSitterProvider::new();
2014        let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
2015
2016        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
2017
2018        assert!(find("greet").exported, "greet should be exported");
2019        assert!(find("add").exported, "add should be exported");
2020        assert!(
2021            find("UserService").exported,
2022            "UserService should be exported"
2023        );
2024        assert!(find("Config").exported, "Config should be exported");
2025        assert!(find("Status").exported, "Status should be exported");
2026        assert!(
2027            !find("internalHelper").exported,
2028            "internalHelper should not be exported"
2029        );
2030    }
2031
2032    #[test]
2033    fn ts_method_scope_chain() {
2034        let provider = TreeSitterProvider::new();
2035        let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
2036
2037        let methods: Vec<&Symbol> = symbols
2038            .iter()
2039            .filter(|s| s.kind == SymbolKind::Method)
2040            .collect();
2041        assert!(!methods.is_empty(), "should have at least one method");
2042
2043        for method in &methods {
2044            assert_eq!(
2045                method.scope_chain,
2046                vec!["UserService"],
2047                "method {} should have UserService in scope chain",
2048                method.name
2049            );
2050            assert_eq!(method.parent.as_deref(), Some("UserService"));
2051        }
2052    }
2053
2054    #[test]
2055    fn ts_signatures_present() {
2056        let provider = TreeSitterProvider::new();
2057        let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
2058
2059        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
2060
2061        let greet_sig = find("greet").signature.as_ref().unwrap();
2062        assert!(
2063            greet_sig.contains("greet"),
2064            "signature should contain function name: {}",
2065            greet_sig
2066        );
2067    }
2068
2069    #[test]
2070    fn ts_ranges_valid() {
2071        let provider = TreeSitterProvider::new();
2072        let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
2073
2074        for s in &symbols {
2075            assert!(
2076                s.range.end_line >= s.range.start_line,
2077                "symbol {} has invalid range: {:?}",
2078                s.name,
2079                s.range
2080            );
2081        }
2082    }
2083
2084    // --- JavaScript extraction ---
2085
2086    #[test]
2087    fn js_extracts_core_symbols() {
2088        let provider = TreeSitterProvider::new();
2089        let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
2090
2091        let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
2092        assert!(
2093            names.contains(&"multiply"),
2094            "missing function multiply: {:?}",
2095            names
2096        );
2097        assert!(
2098            names.contains(&"divide"),
2099            "missing arrow fn divide: {:?}",
2100            names
2101        );
2102        assert!(
2103            names.contains(&"EventEmitter"),
2104            "missing class EventEmitter: {:?}",
2105            names
2106        );
2107        assert!(
2108            names.contains(&"main"),
2109            "missing default export fn main: {:?}",
2110            names
2111        );
2112
2113        assert!(
2114            symbols.len() >= 4,
2115            "expected ≥4 symbols, got {}: {:?}",
2116            symbols.len(),
2117            names
2118        );
2119    }
2120
2121    #[test]
2122    fn js_arrow_fn_correctly_named() {
2123        let provider = TreeSitterProvider::new();
2124        let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
2125
2126        let divide = symbols.iter().find(|s| s.name == "divide").unwrap();
2127        assert_eq!(divide.kind, SymbolKind::Function);
2128        assert!(divide.exported, "divide should be exported");
2129
2130        let internal = symbols.iter().find(|s| s.name == "internalUtil").unwrap();
2131        assert_eq!(internal.kind, SymbolKind::Function);
2132        assert!(!internal.exported, "internalUtil should not be exported");
2133    }
2134
2135    #[test]
2136    fn js_method_scope_chain() {
2137        let provider = TreeSitterProvider::new();
2138        let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
2139
2140        let methods: Vec<&Symbol> = symbols
2141            .iter()
2142            .filter(|s| s.kind == SymbolKind::Method)
2143            .collect();
2144
2145        for method in &methods {
2146            assert_eq!(
2147                method.scope_chain,
2148                vec!["EventEmitter"],
2149                "method {} should have EventEmitter in scope chain",
2150                method.name
2151            );
2152        }
2153    }
2154
2155    // --- TSX extraction ---
2156
2157    #[test]
2158    fn tsx_extracts_react_component() {
2159        let provider = TreeSitterProvider::new();
2160        let symbols = provider.list_symbols(&fixture_path("sample.tsx")).unwrap();
2161
2162        let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
2163        assert!(
2164            names.contains(&"Button"),
2165            "missing React component Button: {:?}",
2166            names
2167        );
2168        assert!(
2169            names.contains(&"Counter"),
2170            "missing class Counter: {:?}",
2171            names
2172        );
2173        assert!(
2174            names.contains(&"formatLabel"),
2175            "missing function formatLabel: {:?}",
2176            names
2177        );
2178
2179        assert!(
2180            symbols.len() >= 2,
2181            "expected ≥2 symbols, got {}: {:?}",
2182            symbols.len(),
2183            names
2184        );
2185    }
2186
2187    #[test]
2188    fn tsx_jsx_doesnt_break_parser() {
2189        // Main assertion: TSX grammar handles JSX without errors
2190        let provider = TreeSitterProvider::new();
2191        let result = provider.list_symbols(&fixture_path("sample.tsx"));
2192        assert!(
2193            result.is_ok(),
2194            "TSX parsing should succeed: {:?}",
2195            result.err()
2196        );
2197    }
2198
2199    // --- resolve_symbol ---
2200
2201    #[test]
2202    fn resolve_symbol_finds_match() {
2203        let provider = TreeSitterProvider::new();
2204        let matches = provider
2205            .resolve_symbol(&fixture_path("sample.ts"), "greet")
2206            .unwrap();
2207        assert_eq!(matches.len(), 1);
2208        assert_eq!(matches[0].symbol.name, "greet");
2209        assert_eq!(matches[0].symbol.kind, SymbolKind::Function);
2210    }
2211
2212    #[test]
2213    fn resolve_symbol_not_found() {
2214        let provider = TreeSitterProvider::new();
2215        let result = provider.resolve_symbol(&fixture_path("sample.ts"), "nonexistent");
2216        assert!(matches!(result, Err(AftError::SymbolNotFound { .. })));
2217    }
2218
2219    #[test]
2220    fn resolve_symbol_follows_reexport_chains() {
2221        let dir = tempfile::tempdir().unwrap();
2222        let config = dir.path().join("config.ts");
2223        let barrel1 = dir.path().join("barrel1.ts");
2224        let barrel2 = dir.path().join("barrel2.ts");
2225        let barrel3 = dir.path().join("barrel3.ts");
2226        let index = dir.path().join("index.ts");
2227
2228        std::fs::write(
2229            &config,
2230            "export class Config {}\nexport default class DefaultConfig {}\n",
2231        )
2232        .unwrap();
2233        std::fs::write(
2234            &barrel1,
2235            "export { Config } from './config';\nexport { default as NamedDefault } from './config';\n",
2236        )
2237        .unwrap();
2238        std::fs::write(
2239            &barrel2,
2240            "export { Config as RenamedConfig } from './barrel1';\n",
2241        )
2242        .unwrap();
2243        std::fs::write(
2244            &barrel3,
2245            "export * from './barrel2';\nexport * from './barrel1';\n",
2246        )
2247        .unwrap();
2248        std::fs::write(
2249            &index,
2250            "export class Config {}\nexport { RenamedConfig as FinalConfig } from './barrel3';\nexport * from './barrel3';\n",
2251        )
2252        .unwrap();
2253
2254        let provider = TreeSitterProvider::new();
2255        let config_canon = std::fs::canonicalize(&config).unwrap();
2256
2257        let direct = provider.resolve_symbol(&barrel1, "Config").unwrap();
2258        assert_eq!(direct.len(), 1);
2259        assert_eq!(direct[0].symbol.name, "Config");
2260        assert_eq!(direct[0].file, config_canon.display().to_string());
2261
2262        let renamed = provider.resolve_symbol(&barrel2, "RenamedConfig").unwrap();
2263        assert_eq!(renamed.len(), 1);
2264        assert_eq!(renamed[0].symbol.name, "Config");
2265        assert_eq!(renamed[0].file, config_canon.display().to_string());
2266
2267        let wildcard_chain = provider.resolve_symbol(&index, "FinalConfig").unwrap();
2268        assert_eq!(wildcard_chain.len(), 1);
2269        assert_eq!(wildcard_chain[0].symbol.name, "Config");
2270        assert_eq!(wildcard_chain[0].file, config_canon.display().to_string());
2271
2272        let wildcard_default = provider.resolve_symbol(&index, "NamedDefault").unwrap();
2273        assert_eq!(wildcard_default.len(), 1);
2274        assert_eq!(wildcard_default[0].symbol.name, "DefaultConfig");
2275        assert_eq!(wildcard_default[0].file, config_canon.display().to_string());
2276
2277        let local = provider.resolve_symbol(&index, "Config").unwrap();
2278        assert_eq!(local.len(), 1);
2279        assert_eq!(local[0].symbol.name, "Config");
2280        assert_eq!(local[0].file, index.display().to_string());
2281    }
2282
2283    // --- Parse tree caching ---
2284
2285    #[test]
2286    fn symbol_range_includes_rust_attributes() {
2287        let dir = tempfile::tempdir().unwrap();
2288        let path = dir.path().join("test_attrs.rs");
2289        std::fs::write(
2290            &path,
2291            "/// This is a doc comment\n#[test]\n#[cfg(test)]\nfn my_test_fn() {\n    assert!(true);\n}\n",
2292        )
2293        .unwrap();
2294
2295        let provider = TreeSitterProvider::new();
2296        let matches = provider.resolve_symbol(&path, "my_test_fn").unwrap();
2297        assert_eq!(matches.len(), 1);
2298        assert_eq!(
2299            matches[0].symbol.range.start_line, 0,
2300            "symbol range should include preceding /// doc comment, got start_line={}",
2301            matches[0].symbol.range.start_line
2302        );
2303    }
2304
2305    #[test]
2306    fn symbol_range_includes_go_doc_comment() {
2307        let dir = tempfile::tempdir().unwrap();
2308        let path = dir.path().join("test_doc.go");
2309        std::fs::write(
2310            &path,
2311            "package main\n\n// MyFunc does something useful.\n// It has a multi-line doc.\nfunc MyFunc() {\n}\n",
2312        )
2313        .unwrap();
2314
2315        let provider = TreeSitterProvider::new();
2316        let matches = provider.resolve_symbol(&path, "MyFunc").unwrap();
2317        assert_eq!(matches.len(), 1);
2318        assert_eq!(
2319            matches[0].symbol.range.start_line, 2,
2320            "symbol range should include preceding doc comments, got start_line={}",
2321            matches[0].symbol.range.start_line
2322        );
2323    }
2324
2325    #[test]
2326    fn symbol_range_skips_unrelated_comments() {
2327        let dir = tempfile::tempdir().unwrap();
2328        let path = dir.path().join("test_gap.go");
2329        std::fs::write(
2330            &path,
2331            "package main\n\n// This is a standalone comment\n\nfunc Standalone() {\n}\n",
2332        )
2333        .unwrap();
2334
2335        let provider = TreeSitterProvider::new();
2336        let matches = provider.resolve_symbol(&path, "Standalone").unwrap();
2337        assert_eq!(matches.len(), 1);
2338        assert_eq!(
2339            matches[0].symbol.range.start_line, 4,
2340            "symbol range should NOT include comment separated by blank line, got start_line={}",
2341            matches[0].symbol.range.start_line
2342        );
2343    }
2344
2345    #[test]
2346    fn parse_cache_returns_same_tree() {
2347        let mut parser = FileParser::new();
2348        let path = fixture_path("sample.ts");
2349
2350        let (tree1, _) = parser.parse(&path).unwrap();
2351        let tree1_root = tree1.root_node().byte_range();
2352
2353        let (tree2, _) = parser.parse(&path).unwrap();
2354        let tree2_root = tree2.root_node().byte_range();
2355
2356        // Same tree (cache hit) should return identical root node range
2357        assert_eq!(tree1_root, tree2_root);
2358    }
2359
2360    // --- Python extraction ---
2361
2362    #[test]
2363    fn py_extracts_all_symbols() {
2364        let provider = TreeSitterProvider::new();
2365        let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
2366
2367        let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
2368        assert!(
2369            names.contains(&"top_level_function"),
2370            "missing top_level_function: {:?}",
2371            names
2372        );
2373        assert!(names.contains(&"MyClass"), "missing MyClass: {:?}", names);
2374        assert!(
2375            names.contains(&"instance_method"),
2376            "missing method instance_method: {:?}",
2377            names
2378        );
2379        assert!(
2380            names.contains(&"decorated_function"),
2381            "missing decorated_function: {:?}",
2382            names
2383        );
2384
2385        // Plan requires ≥4 symbols
2386        assert!(
2387            symbols.len() >= 4,
2388            "expected ≥4 symbols, got {}: {:?}",
2389            symbols.len(),
2390            names
2391        );
2392    }
2393
2394    #[test]
2395    fn py_symbol_kinds_correct() {
2396        let provider = TreeSitterProvider::new();
2397        let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
2398
2399        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
2400
2401        assert_eq!(find("top_level_function").kind, SymbolKind::Function);
2402        assert_eq!(find("MyClass").kind, SymbolKind::Class);
2403        assert_eq!(find("instance_method").kind, SymbolKind::Method);
2404        assert_eq!(find("decorated_function").kind, SymbolKind::Function);
2405        assert_eq!(find("OuterClass").kind, SymbolKind::Class);
2406        assert_eq!(find("InnerClass").kind, SymbolKind::Class);
2407        assert_eq!(find("inner_method").kind, SymbolKind::Method);
2408        assert_eq!(find("outer_method").kind, SymbolKind::Method);
2409    }
2410
2411    #[test]
2412    fn py_method_scope_chain() {
2413        let provider = TreeSitterProvider::new();
2414        let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
2415
2416        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
2417
2418        // Method inside MyClass
2419        assert_eq!(
2420            find("instance_method").scope_chain,
2421            vec!["MyClass"],
2422            "instance_method should have MyClass in scope chain"
2423        );
2424        assert_eq!(find("instance_method").parent.as_deref(), Some("MyClass"));
2425
2426        // Method inside OuterClass > InnerClass
2427        assert_eq!(
2428            find("inner_method").scope_chain,
2429            vec!["OuterClass", "InnerClass"],
2430            "inner_method should have nested scope chain"
2431        );
2432
2433        // InnerClass itself should have OuterClass in scope
2434        assert_eq!(
2435            find("InnerClass").scope_chain,
2436            vec!["OuterClass"],
2437            "InnerClass should have OuterClass in scope"
2438        );
2439
2440        // Top-level function has empty scope
2441        assert!(
2442            find("top_level_function").scope_chain.is_empty(),
2443            "top-level function should have empty scope chain"
2444        );
2445    }
2446
2447    #[test]
2448    fn py_decorated_function_signature() {
2449        let provider = TreeSitterProvider::new();
2450        let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
2451
2452        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
2453
2454        let sig = find("decorated_function").signature.as_ref().unwrap();
2455        assert!(
2456            sig.contains("@staticmethod"),
2457            "decorated function signature should include decorator: {}",
2458            sig
2459        );
2460        assert!(
2461            sig.contains("def decorated_function"),
2462            "signature should include function def: {}",
2463            sig
2464        );
2465    }
2466
2467    #[test]
2468    fn py_ranges_valid() {
2469        let provider = TreeSitterProvider::new();
2470        let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
2471
2472        for s in &symbols {
2473            assert!(
2474                s.range.end_line >= s.range.start_line,
2475                "symbol {} has invalid range: {:?}",
2476                s.name,
2477                s.range
2478            );
2479        }
2480    }
2481
2482    // --- Rust extraction ---
2483
2484    #[test]
2485    fn rs_extracts_all_symbols() {
2486        let provider = TreeSitterProvider::new();
2487        let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
2488
2489        let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
2490        assert!(
2491            names.contains(&"public_function"),
2492            "missing public_function: {:?}",
2493            names
2494        );
2495        assert!(
2496            names.contains(&"private_function"),
2497            "missing private_function: {:?}",
2498            names
2499        );
2500        assert!(names.contains(&"MyStruct"), "missing MyStruct: {:?}", names);
2501        assert!(names.contains(&"Color"), "missing enum Color: {:?}", names);
2502        assert!(
2503            names.contains(&"Drawable"),
2504            "missing trait Drawable: {:?}",
2505            names
2506        );
2507        // impl methods
2508        assert!(
2509            names.contains(&"new"),
2510            "missing impl method new: {:?}",
2511            names
2512        );
2513
2514        // Plan requires ≥6 symbols
2515        assert!(
2516            symbols.len() >= 6,
2517            "expected ≥6 symbols, got {}: {:?}",
2518            symbols.len(),
2519            names
2520        );
2521    }
2522
2523    #[test]
2524    fn rs_symbol_kinds_correct() {
2525        let provider = TreeSitterProvider::new();
2526        let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
2527
2528        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
2529
2530        assert_eq!(find("public_function").kind, SymbolKind::Function);
2531        assert_eq!(find("private_function").kind, SymbolKind::Function);
2532        assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
2533        assert_eq!(find("Color").kind, SymbolKind::Enum);
2534        assert_eq!(find("Drawable").kind, SymbolKind::Interface); // trait → Interface
2535        assert_eq!(find("new").kind, SymbolKind::Method);
2536    }
2537
2538    #[test]
2539    fn rs_pub_export_detection() {
2540        let provider = TreeSitterProvider::new();
2541        let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
2542
2543        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
2544
2545        assert!(
2546            find("public_function").exported,
2547            "pub fn should be exported"
2548        );
2549        assert!(
2550            !find("private_function").exported,
2551            "non-pub fn should not be exported"
2552        );
2553        assert!(find("MyStruct").exported, "pub struct should be exported");
2554        assert!(find("Color").exported, "pub enum should be exported");
2555        assert!(find("Drawable").exported, "pub trait should be exported");
2556        assert!(
2557            find("new").exported,
2558            "pub fn inside impl should be exported"
2559        );
2560        assert!(
2561            !find("helper").exported,
2562            "non-pub fn inside impl should not be exported"
2563        );
2564    }
2565
2566    #[test]
2567    fn rs_impl_method_scope_chain() {
2568        let provider = TreeSitterProvider::new();
2569        let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
2570
2571        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
2572
2573        // `impl MyStruct { fn new() }` → scope chain = ["MyStruct"]
2574        assert_eq!(
2575            find("new").scope_chain,
2576            vec!["MyStruct"],
2577            "impl method should have type in scope chain"
2578        );
2579        assert_eq!(find("new").parent.as_deref(), Some("MyStruct"));
2580
2581        // Free function has empty scope chain
2582        assert!(
2583            find("public_function").scope_chain.is_empty(),
2584            "free function should have empty scope chain"
2585        );
2586    }
2587
2588    #[test]
2589    fn rs_trait_impl_scope_chain() {
2590        let provider = TreeSitterProvider::new();
2591        let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
2592
2593        // `impl Drawable for MyStruct { fn draw() }` → scope = ["Drawable for MyStruct"]
2594        let draw = symbols.iter().find(|s| s.name == "draw").unwrap();
2595        assert_eq!(
2596            draw.scope_chain,
2597            vec!["Drawable for MyStruct"],
2598            "trait impl method should have 'Trait for Type' scope"
2599        );
2600        assert_eq!(draw.parent.as_deref(), Some("MyStruct"));
2601    }
2602
2603    #[test]
2604    fn rs_ranges_valid() {
2605        let provider = TreeSitterProvider::new();
2606        let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
2607
2608        for s in &symbols {
2609            assert!(
2610                s.range.end_line >= s.range.start_line,
2611                "symbol {} has invalid range: {:?}",
2612                s.name,
2613                s.range
2614            );
2615        }
2616    }
2617
2618    // --- Go extraction ---
2619
2620    #[test]
2621    fn go_extracts_all_symbols() {
2622        let provider = TreeSitterProvider::new();
2623        let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
2624
2625        let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
2626        assert!(
2627            names.contains(&"ExportedFunction"),
2628            "missing ExportedFunction: {:?}",
2629            names
2630        );
2631        assert!(
2632            names.contains(&"unexportedFunction"),
2633            "missing unexportedFunction: {:?}",
2634            names
2635        );
2636        assert!(
2637            names.contains(&"MyStruct"),
2638            "missing struct MyStruct: {:?}",
2639            names
2640        );
2641        assert!(
2642            names.contains(&"Reader"),
2643            "missing interface Reader: {:?}",
2644            names
2645        );
2646        // receiver method
2647        assert!(
2648            names.contains(&"String"),
2649            "missing receiver method String: {:?}",
2650            names
2651        );
2652
2653        // Plan requires ≥4 symbols
2654        assert!(
2655            symbols.len() >= 4,
2656            "expected ≥4 symbols, got {}: {:?}",
2657            symbols.len(),
2658            names
2659        );
2660    }
2661
2662    #[test]
2663    fn go_symbol_kinds_correct() {
2664        let provider = TreeSitterProvider::new();
2665        let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
2666
2667        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
2668
2669        assert_eq!(find("ExportedFunction").kind, SymbolKind::Function);
2670        assert_eq!(find("unexportedFunction").kind, SymbolKind::Function);
2671        assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
2672        assert_eq!(find("Reader").kind, SymbolKind::Interface);
2673        assert_eq!(find("String").kind, SymbolKind::Method);
2674        assert_eq!(find("helper").kind, SymbolKind::Method);
2675    }
2676
2677    #[test]
2678    fn go_uppercase_export_detection() {
2679        let provider = TreeSitterProvider::new();
2680        let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
2681
2682        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
2683
2684        assert!(
2685            find("ExportedFunction").exported,
2686            "ExportedFunction (uppercase) should be exported"
2687        );
2688        assert!(
2689            !find("unexportedFunction").exported,
2690            "unexportedFunction (lowercase) should not be exported"
2691        );
2692        assert!(
2693            find("MyStruct").exported,
2694            "MyStruct (uppercase) should be exported"
2695        );
2696        assert!(
2697            find("Reader").exported,
2698            "Reader (uppercase) should be exported"
2699        );
2700        assert!(
2701            find("String").exported,
2702            "String method (uppercase) should be exported"
2703        );
2704        assert!(
2705            !find("helper").exported,
2706            "helper method (lowercase) should not be exported"
2707        );
2708    }
2709
2710    #[test]
2711    fn go_receiver_method_scope_chain() {
2712        let provider = TreeSitterProvider::new();
2713        let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
2714
2715        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
2716
2717        // `func (m *MyStruct) String()` → scope chain = ["MyStruct"]
2718        assert_eq!(
2719            find("String").scope_chain,
2720            vec!["MyStruct"],
2721            "receiver method should have type in scope chain"
2722        );
2723        assert_eq!(find("String").parent.as_deref(), Some("MyStruct"));
2724
2725        // Regular function has empty scope chain
2726        assert!(
2727            find("ExportedFunction").scope_chain.is_empty(),
2728            "regular function should have empty scope chain"
2729        );
2730    }
2731
2732    #[test]
2733    fn go_ranges_valid() {
2734        let provider = TreeSitterProvider::new();
2735        let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
2736
2737        for s in &symbols {
2738            assert!(
2739                s.range.end_line >= s.range.start_line,
2740                "symbol {} has invalid range: {:?}",
2741                s.name,
2742                s.range
2743            );
2744        }
2745    }
2746
2747    // --- Cross-language ---
2748
2749    #[test]
2750    fn cross_language_all_six_produce_symbols() {
2751        let provider = TreeSitterProvider::new();
2752
2753        let fixtures = [
2754            ("sample.ts", "TypeScript"),
2755            ("sample.tsx", "TSX"),
2756            ("sample.js", "JavaScript"),
2757            ("sample.py", "Python"),
2758            ("sample.rs", "Rust"),
2759            ("sample.go", "Go"),
2760        ];
2761
2762        for (fixture, lang) in &fixtures {
2763            let symbols = provider
2764                .list_symbols(&fixture_path(fixture))
2765                .unwrap_or_else(|e| panic!("{} ({}) failed: {:?}", lang, fixture, e));
2766            assert!(
2767                symbols.len() >= 2,
2768                "{} should produce ≥2 symbols, got {}: {:?}",
2769                lang,
2770                symbols.len(),
2771                symbols.iter().map(|s| &s.name).collect::<Vec<_>>()
2772            );
2773        }
2774    }
2775}