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
145const C_QUERY: &str = r#"
146;; function definitions
147(function_definition
148  declarator: (function_declarator
149    declarator: (identifier) @fn.name)) @fn.def
150
151;; function declarations / prototypes
152(declaration
153  declarator: (function_declarator
154    declarator: (identifier) @fn.name)) @fn.def
155
156;; struct declarations
157(struct_specifier
158  name: (type_identifier) @struct.name
159  body: (field_declaration_list)) @struct.def
160
161;; enum declarations
162(enum_specifier
163  name: (type_identifier) @enum.name
164  body: (enumerator_list)) @enum.def
165
166;; typedef aliases
167(type_definition
168  declarator: (type_identifier) @type.name) @type.def
169
170;; macros
171(preproc_def
172  name: (identifier) @macro.name) @macro.def
173
174(preproc_function_def
175  name: (identifier) @macro.name) @macro.def
176"#;
177
178const CPP_QUERY: &str = r#"
179;; free function definitions
180(function_definition
181  declarator: (function_declarator
182    declarator: (identifier) @fn.name)) @fn.def
183
184;; free function declarations
185(declaration
186  declarator: (function_declarator
187    declarator: (identifier) @fn.name)) @fn.def
188
189;; inline method definitions / declarations inside class bodies
190(function_definition
191  declarator: (function_declarator
192    declarator: (field_identifier) @method.name)) @method.def
193
194(field_declaration
195  declarator: (function_declarator
196    declarator: (field_identifier) @method.name)) @method.def
197
198;; qualified functions / methods
199(function_definition
200  declarator: (function_declarator
201    declarator: (qualified_identifier
202      scope: (_) @qual.scope
203      name: (identifier) @qual.name))) @qual.def
204
205(declaration
206  declarator: (function_declarator
207    declarator: (qualified_identifier
208      scope: (_) @qual.scope
209      name: (identifier) @qual.name))) @qual.def
210
211;; class / struct / enum / namespace declarations
212(class_specifier
213  name: (_) @class.name) @class.def
214
215(struct_specifier
216  name: (_) @struct.name) @struct.def
217
218(enum_specifier
219  name: (_) @enum.name) @enum.def
220
221(namespace_definition
222  name: (_) @namespace.name) @namespace.def
223
224;; template declarations
225(template_declaration
226  (class_specifier
227    name: (_) @template.class.name) @template.class.item) @template.class.def
228
229(template_declaration
230  (struct_specifier
231    name: (_) @template.struct.name) @template.struct.item) @template.struct.def
232
233(template_declaration
234  (function_definition
235    declarator: (function_declarator
236      declarator: (identifier) @template.fn.name)) @template.fn.item) @template.fn.def
237
238(template_declaration
239  (function_definition
240    declarator: (function_declarator
241      declarator: (qualified_identifier
242        scope: (_) @template.qual.scope
243        name: (identifier) @template.qual.name))) @template.qual.item) @template.qual.def
244"#;
245
246const ZIG_QUERY: &str = r#"
247;; functions
248(function_declaration
249  name: (identifier) @fn.name) @fn.def
250
251;; container declarations bound to const names
252(variable_declaration
253  (identifier) @struct.name
254  "="
255  (struct_declaration) @struct.body) @struct.def
256
257(variable_declaration
258  (identifier) @enum.name
259  "="
260  (enum_declaration) @enum.body) @enum.def
261
262(variable_declaration
263  (identifier) @union.name
264  "="
265  (union_declaration) @union.body) @union.def
266
267;; const declarations
268(variable_declaration
269  (identifier) @const.name) @const.def
270
271;; tests
272(test_declaration
273  (string) @test.name) @test.def
274
275(test_declaration
276  (identifier) @test.name) @test.def
277"#;
278
279const CSHARP_QUERY: &str = r#"
280;; types
281(class_declaration
282  name: (identifier) @class.name) @class.def
283
284(interface_declaration
285  name: (identifier) @interface.name) @interface.def
286
287(struct_declaration
288  name: (identifier) @struct.name) @struct.def
289
290(enum_declaration
291  name: (identifier) @enum.name) @enum.def
292
293;; members
294(method_declaration
295  name: (identifier) @method.name) @method.def
296
297(property_declaration
298  name: (identifier) @property.name) @property.def
299
300;; namespaces
301(namespace_declaration
302  name: (_) @namespace.name) @namespace.def
303
304(file_scoped_namespace_declaration
305  name: (_) @namespace.name) @namespace.def
306"#;
307
308/// Supported language identifier.
309#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
310pub enum LangId {
311    TypeScript,
312    Tsx,
313    JavaScript,
314    Python,
315    Rust,
316    Go,
317    C,
318    Cpp,
319    Zig,
320    CSharp,
321    Html,
322    Markdown,
323}
324
325/// Maps file extension to language identifier.
326pub fn detect_language(path: &Path) -> Option<LangId> {
327    let ext = path.extension()?.to_str()?;
328    match ext {
329        "ts" => Some(LangId::TypeScript),
330        "tsx" => Some(LangId::Tsx),
331        "js" | "jsx" => Some(LangId::JavaScript),
332        "py" => Some(LangId::Python),
333        "rs" => Some(LangId::Rust),
334        "go" => Some(LangId::Go),
335        "c" | "h" => Some(LangId::C),
336        "cc" | "cpp" | "cxx" | "hpp" | "hh" => Some(LangId::Cpp),
337        "zig" => Some(LangId::Zig),
338        "cs" => Some(LangId::CSharp),
339        "html" | "htm" => Some(LangId::Html),
340        "md" | "markdown" | "mdx" => Some(LangId::Markdown),
341        _ => None,
342    }
343}
344
345/// Returns the tree-sitter Language grammar for a given LangId.
346pub fn grammar_for(lang: LangId) -> Language {
347    match lang {
348        LangId::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
349        LangId::Tsx => tree_sitter_typescript::LANGUAGE_TSX.into(),
350        LangId::JavaScript => tree_sitter_javascript::LANGUAGE.into(),
351        LangId::Python => tree_sitter_python::LANGUAGE.into(),
352        LangId::Rust => tree_sitter_rust::LANGUAGE.into(),
353        LangId::Go => tree_sitter_go::LANGUAGE.into(),
354        LangId::C => tree_sitter_c::LANGUAGE.into(),
355        LangId::Cpp => tree_sitter_cpp::LANGUAGE.into(),
356        LangId::Zig => tree_sitter_zig::LANGUAGE.into(),
357        LangId::CSharp => tree_sitter_c_sharp::LANGUAGE.into(),
358        LangId::Html => tree_sitter_html::LANGUAGE.into(),
359        LangId::Markdown => tree_sitter_md::LANGUAGE.into(),
360    }
361}
362
363/// Returns the query pattern string for a given LangId, if implemented.
364fn query_for(lang: LangId) -> Option<&'static str> {
365    match lang {
366        LangId::TypeScript | LangId::Tsx => Some(TS_QUERY),
367        LangId::JavaScript => Some(JS_QUERY),
368        LangId::Python => Some(PY_QUERY),
369        LangId::Rust => Some(RS_QUERY),
370        LangId::Go => Some(GO_QUERY),
371        LangId::C => Some(C_QUERY),
372        LangId::Cpp => Some(CPP_QUERY),
373        LangId::Zig => Some(ZIG_QUERY),
374        LangId::CSharp => Some(CSHARP_QUERY),
375        LangId::Html => None, // HTML uses direct tree walking like Markdown
376        LangId::Markdown => None,
377    }
378}
379
380/// Cached parse result: mtime at parse time + the tree.
381struct CachedTree {
382    mtime: SystemTime,
383    tree: Tree,
384}
385
386/// Cached symbol extraction result: mtime at extraction time + symbols.
387#[derive(Clone)]
388struct CachedSymbols {
389    mtime: SystemTime,
390    symbols: Vec<Symbol>,
391}
392
393/// Shared symbol cache that can be pre-warmed in a background thread
394/// and merged into the main thread. Thread-safe for building, then
395/// transferred to the single-threaded main loop.
396#[derive(Clone, Default)]
397pub struct SymbolCache {
398    entries: HashMap<PathBuf, CachedSymbols>,
399}
400
401impl SymbolCache {
402    pub fn new() -> Self {
403        Self {
404            entries: HashMap::new(),
405        }
406    }
407
408    /// Insert pre-warmed symbols for a file.
409    pub fn insert(&mut self, path: PathBuf, mtime: SystemTime, symbols: Vec<Symbol>) {
410        self.entries.insert(path, CachedSymbols { mtime, symbols });
411    }
412
413    /// Merge another cache into this one (newer entries win by mtime).
414    pub fn merge(&mut self, other: SymbolCache) {
415        for (path, entry) in other.entries {
416            match self.entries.get(&path) {
417                Some(existing) if existing.mtime >= entry.mtime => {}
418                _ => {
419                    self.entries.insert(path, entry);
420                }
421            }
422        }
423    }
424
425    /// Number of cached entries.
426    pub fn len(&self) -> usize {
427        self.entries.len()
428    }
429}
430
431/// Core parsing engine. Handles language detection, parse tree caching,
432/// symbol table caching, and query pattern execution via tree-sitter.
433pub struct FileParser {
434    cache: HashMap<PathBuf, CachedTree>,
435    symbol_cache: HashMap<PathBuf, CachedSymbols>,
436    /// Shared pre-warmed cache from background indexing
437    warm_cache: Option<SymbolCache>,
438}
439
440impl FileParser {
441    /// Create a new `FileParser` with an empty parse cache.
442    pub fn new() -> Self {
443        Self {
444            cache: HashMap::new(),
445            symbol_cache: HashMap::new(),
446            warm_cache: None,
447        }
448    }
449
450    /// Attach a pre-warmed symbol cache from background indexing.
451    pub fn set_warm_cache(&mut self, cache: SymbolCache) {
452        self.warm_cache = Some(cache);
453    }
454
455    /// Number of entries in the local symbol cache.
456    pub fn symbol_cache_len(&self) -> usize {
457        self.symbol_cache.len()
458    }
459
460    /// Number of entries in the warm (pre-warmed) symbol cache.
461    pub fn warm_cache_len(&self) -> usize {
462        self.warm_cache.as_ref().map_or(0, |c| c.len())
463    }
464
465    /// Parse a file, returning the tree and detected language. Uses cache if
466    /// the file hasn't been modified since last parse.
467    pub fn parse(&mut self, path: &Path) -> Result<(&Tree, LangId), AftError> {
468        let lang = detect_language(path).ok_or_else(|| AftError::InvalidRequest {
469            message: format!(
470                "unsupported file extension: {}",
471                path.extension()
472                    .and_then(|e| e.to_str())
473                    .unwrap_or("<none>")
474            ),
475        })?;
476
477        let canon = path.to_path_buf();
478        let current_mtime = std::fs::metadata(path)
479            .and_then(|m| m.modified())
480            .map_err(|e| AftError::FileNotFound {
481                path: format!("{}: {}", path.display(), e),
482            })?;
483
484        // Check cache validity
485        let needs_reparse = match self.cache.get(&canon) {
486            Some(cached) => cached.mtime != current_mtime,
487            None => true,
488        };
489
490        if needs_reparse {
491            let source = std::fs::read_to_string(path).map_err(|e| AftError::FileNotFound {
492                path: format!("{}: {}", path.display(), e),
493            })?;
494
495            let grammar = grammar_for(lang);
496            let mut parser = Parser::new();
497            parser.set_language(&grammar).map_err(|e| {
498                log::error!("grammar init failed for {:?}: {}", lang, e);
499                AftError::ParseError {
500                    message: format!("grammar init failed for {:?}: {}", lang, e),
501                }
502            })?;
503
504            let tree = parser.parse(&source, None).ok_or_else(|| {
505                log::error!("parse failed for {}", path.display());
506                AftError::ParseError {
507                    message: format!("tree-sitter parse returned None for {}", path.display()),
508                }
509            })?;
510
511            self.cache.insert(
512                canon.clone(),
513                CachedTree {
514                    mtime: current_mtime,
515                    tree,
516                },
517            );
518        }
519
520        let cached = self.cache.get(&canon).ok_or_else(|| AftError::ParseError {
521            message: format!("parser cache missing entry for {}", path.display()),
522        })?;
523        Ok((&cached.tree, lang))
524    }
525
526    /// Like [`FileParser::parse`] but returns an owned `Tree` clone.
527    ///
528    /// Useful when the caller needs to hold the tree while also calling
529    /// other mutable methods on this parser.
530    pub fn parse_cloned(&mut self, path: &Path) -> Result<(Tree, LangId), AftError> {
531        let (tree, lang) = self.parse(path)?;
532        Ok((tree.clone(), lang))
533    }
534
535    /// Extract symbols from a file using language-specific query patterns.
536    /// Results are cached by `(path, mtime)` — subsequent calls for unchanged
537    /// files return the cached symbol table without re-parsing.
538    pub fn extract_symbols(&mut self, path: &Path) -> Result<Vec<Symbol>, AftError> {
539        let canon = path.to_path_buf();
540        let current_mtime = std::fs::metadata(path)
541            .and_then(|m| m.modified())
542            .map_err(|e| AftError::FileNotFound {
543                path: format!("{}: {}", path.display(), e),
544            })?;
545
546        // Return cached symbols if file hasn't changed (local cache first, then warm cache)
547        if let Some(cached) = self.symbol_cache.get(&canon) {
548            if cached.mtime == current_mtime {
549                return Ok(cached.symbols.clone());
550            }
551        }
552        if let Some(warm) = &self.warm_cache {
553            if let Some(cached) = warm.entries.get(&canon) {
554                if cached.mtime == current_mtime {
555                    // Promote to local cache for future lookups
556                    self.symbol_cache.insert(canon, cached.clone());
557                    return Ok(cached.symbols.clone());
558                }
559            }
560        }
561
562        let source = std::fs::read_to_string(path).map_err(|e| AftError::FileNotFound {
563            path: format!("{}: {}", path.display(), e),
564        })?;
565
566        let (tree, lang) = self.parse(path)?;
567        let root = tree.root_node();
568
569        // HTML and Markdown use direct tree walking, not query patterns
570        let symbols = if lang == LangId::Html {
571            extract_html_symbols(&source, &root)?
572        } else if lang == LangId::Markdown {
573            extract_md_symbols(&source, &root)?
574        } else {
575            let query_src = query_for(lang).ok_or_else(|| AftError::InvalidRequest {
576                message: format!("no query patterns implemented for {:?} yet", lang),
577            })?;
578
579            let grammar = grammar_for(lang);
580            let query = Query::new(&grammar, query_src).map_err(|e| {
581                log::error!("query compile failed for {:?}: {}", lang, e);
582                AftError::ParseError {
583                    message: format!("query compile error for {:?}: {}", lang, e),
584                }
585            })?;
586
587            match lang {
588                LangId::TypeScript | LangId::Tsx => extract_ts_symbols(&source, &root, &query)?,
589                LangId::JavaScript => extract_js_symbols(&source, &root, &query)?,
590                LangId::Python => extract_py_symbols(&source, &root, &query)?,
591                LangId::Rust => extract_rs_symbols(&source, &root, &query)?,
592                LangId::Go => extract_go_symbols(&source, &root, &query)?,
593                LangId::C => extract_c_symbols(&source, &root, &query)?,
594                LangId::Cpp => extract_cpp_symbols(&source, &root, &query)?,
595                LangId::Zig => extract_zig_symbols(&source, &root, &query)?,
596                LangId::CSharp => extract_csharp_symbols(&source, &root, &query)?,
597                LangId::Html | LangId::Markdown => vec![],
598            }
599        };
600
601        // Cache the result
602        self.symbol_cache.insert(
603            canon,
604            CachedSymbols {
605                mtime: current_mtime,
606                symbols: symbols.clone(),
607            },
608        );
609
610        Ok(symbols)
611    }
612
613    /// Invalidate cached symbols for a specific file (e.g., after an edit).
614    pub fn invalidate_symbols(&mut self, path: &Path) {
615        self.symbol_cache.remove(path);
616        self.cache.remove(path);
617    }
618}
619
620/// Build a Range from a tree-sitter Node.
621pub(crate) fn node_range(node: &Node) -> Range {
622    let start = node.start_position();
623    let end = node.end_position();
624    Range {
625        start_line: start.row as u32,
626        start_col: start.column as u32,
627        end_line: end.row as u32,
628        end_col: end.column as u32,
629    }
630}
631
632/// Build a Range from a tree-sitter Node, expanding upward to include
633/// preceding attributes, decorators, and doc comments that belong to the symbol.
634///
635/// This ensures that when agents edit/replace a symbol, they get the full
636/// declaration including `#[test]`, `#[derive(...)]`, `/// doc`, `@decorator`, etc.
637pub(crate) fn node_range_with_decorators(node: &Node, source: &str, lang: LangId) -> Range {
638    let mut range = node_range(node);
639
640    let mut current = *node;
641    while let Some(prev) = current.prev_sibling() {
642        let kind = prev.kind();
643        let should_include = match lang {
644            LangId::Rust => {
645                // Include #[...] attributes
646                kind == "attribute_item"
647                    // Include /// doc comments (but not regular // comments)
648                    || (kind == "line_comment"
649                        && node_text(source, &prev).starts_with("///"))
650                    // Include /** ... */ doc comments
651                    || (kind == "block_comment"
652                        && node_text(source, &prev).starts_with("/**"))
653            }
654            LangId::TypeScript | LangId::Tsx | LangId::JavaScript => {
655                // Include @decorator
656                kind == "decorator"
657                    // Include /** JSDoc */ comments
658                    || (kind == "comment"
659                        && node_text(source, &prev).starts_with("/**"))
660            }
661            LangId::Go | LangId::C | LangId::Cpp | LangId::Zig | LangId::CSharp => {
662                // Include doc comments only if immediately above (no blank line gap)
663                kind == "comment" && is_adjacent_line(&prev, &current, source)
664            }
665            LangId::Python => {
666                // Decorators are handled by decorated_definition capture
667                false
668            }
669            LangId::Html | LangId::Markdown => false,
670        };
671
672        if should_include {
673            range.start_line = prev.start_position().row as u32;
674            range.start_col = prev.start_position().column as u32;
675            current = prev;
676        } else {
677            break;
678        }
679    }
680
681    range
682}
683
684/// Check if two nodes are on adjacent lines (no blank line between them).
685fn is_adjacent_line(upper: &Node, lower: &Node, source: &str) -> bool {
686    let upper_end = upper.end_position().row;
687    let lower_start = lower.start_position().row;
688
689    if lower_start == 0 || lower_start <= upper_end {
690        return true;
691    }
692
693    // Check that there's no blank line between them
694    let lines: Vec<&str> = source.lines().collect();
695    for row in (upper_end + 1)..lower_start {
696        if row < lines.len() && lines[row].trim().is_empty() {
697            return false;
698        }
699    }
700    true
701}
702
703/// Extract the text of a node from source.
704pub(crate) fn node_text<'a>(source: &'a str, node: &Node) -> &'a str {
705    &source[node.byte_range()]
706}
707
708fn lexical_declaration_has_function_value(node: &Node) -> bool {
709    let mut cursor = node.walk();
710    if !cursor.goto_first_child() {
711        return false;
712    }
713
714    loop {
715        let child = cursor.node();
716        if matches!(
717            child.kind(),
718            "arrow_function" | "function_expression" | "generator_function"
719        ) {
720            return true;
721        }
722
723        if lexical_declaration_has_function_value(&child) {
724            return true;
725        }
726
727        if !cursor.goto_next_sibling() {
728            break;
729        }
730    }
731
732    false
733}
734
735/// Collect byte ranges of all export_statement nodes from query matches.
736fn collect_export_ranges(source: &str, root: &Node, query: &Query) -> Vec<std::ops::Range<usize>> {
737    let export_idx = query
738        .capture_names()
739        .iter()
740        .position(|n| *n == "export.stmt");
741    let export_idx = match export_idx {
742        Some(i) => i as u32,
743        None => return vec![],
744    };
745
746    let mut cursor = QueryCursor::new();
747    let mut ranges = Vec::new();
748    let mut matches = cursor.matches(query, *root, source.as_bytes());
749
750    while let Some(m) = {
751        matches.advance();
752        matches.get()
753    } {
754        for cap in m.captures {
755            if cap.index == export_idx {
756                ranges.push(cap.node.byte_range());
757            }
758        }
759    }
760    ranges
761}
762
763/// Check if a node's byte range is contained within any export statement.
764fn is_exported(node: &Node, export_ranges: &[std::ops::Range<usize>]) -> bool {
765    let r = node.byte_range();
766    export_ranges
767        .iter()
768        .any(|er| er.start <= r.start && r.end <= er.end)
769}
770
771/// Extract the first line of a node as its signature.
772fn extract_signature(source: &str, node: &Node) -> String {
773    let text = node_text(source, node);
774    let first_line = text.lines().next().unwrap_or(text);
775    // Trim trailing opening brace if present
776    let trimmed = first_line.trim_end();
777    let trimmed = trimmed.strip_suffix('{').unwrap_or(trimmed).trim_end();
778    trimmed.to_string()
779}
780
781/// Extract symbols from TypeScript / TSX source.
782fn extract_ts_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
783    let lang = LangId::TypeScript;
784    let capture_names = query.capture_names();
785
786    let export_ranges = collect_export_ranges(source, root, query);
787
788    let mut symbols = Vec::new();
789    let mut cursor = QueryCursor::new();
790    let mut matches = cursor.matches(query, *root, source.as_bytes());
791
792    while let Some(m) = {
793        matches.advance();
794        matches.get()
795    } {
796        // Determine what kind of match this is by looking at capture names
797        let mut fn_name_node = None;
798        let mut fn_def_node = None;
799        let mut arrow_name_node = None;
800        let mut arrow_def_node = None;
801        let mut class_name_node = None;
802        let mut class_def_node = None;
803        let mut method_class_name_node = None;
804        let mut method_name_node = None;
805        let mut method_def_node = None;
806        let mut interface_name_node = None;
807        let mut interface_def_node = None;
808        let mut enum_name_node = None;
809        let mut enum_def_node = None;
810        let mut type_alias_name_node = None;
811        let mut type_alias_def_node = None;
812        let mut var_name_node = None;
813        let mut var_def_node = None;
814
815        for cap in m.captures {
816            let Some(&name) = capture_names.get(cap.index as usize) else {
817                continue;
818            };
819            match name {
820                "fn.name" => fn_name_node = Some(cap.node),
821                "fn.def" => fn_def_node = Some(cap.node),
822                "arrow.name" => arrow_name_node = Some(cap.node),
823                "arrow.def" => arrow_def_node = Some(cap.node),
824                "class.name" => class_name_node = Some(cap.node),
825                "class.def" => class_def_node = Some(cap.node),
826                "method.class_name" => method_class_name_node = Some(cap.node),
827                "method.name" => method_name_node = Some(cap.node),
828                "method.def" => method_def_node = Some(cap.node),
829                "interface.name" => interface_name_node = Some(cap.node),
830                "interface.def" => interface_def_node = Some(cap.node),
831                "enum.name" => enum_name_node = Some(cap.node),
832                "enum.def" => enum_def_node = Some(cap.node),
833                "type_alias.name" => type_alias_name_node = Some(cap.node),
834                "type_alias.def" => type_alias_def_node = Some(cap.node),
835                "var.name" => var_name_node = Some(cap.node),
836                "var.def" => var_def_node = Some(cap.node),
837                // var.value/var.decl removed — not needed
838                _ => {}
839            }
840        }
841
842        // Function declaration
843        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
844            symbols.push(Symbol {
845                name: node_text(source, &name_node).to_string(),
846                kind: SymbolKind::Function,
847                range: node_range_with_decorators(&def_node, source, lang),
848                signature: Some(extract_signature(source, &def_node)),
849                scope_chain: vec![],
850                exported: is_exported(&def_node, &export_ranges),
851                parent: None,
852            });
853        }
854
855        // Arrow function
856        if let (Some(name_node), Some(def_node)) = (arrow_name_node, arrow_def_node) {
857            symbols.push(Symbol {
858                name: node_text(source, &name_node).to_string(),
859                kind: SymbolKind::Function,
860                range: node_range_with_decorators(&def_node, source, lang),
861                signature: Some(extract_signature(source, &def_node)),
862                scope_chain: vec![],
863                exported: is_exported(&def_node, &export_ranges),
864                parent: None,
865            });
866        }
867
868        // Class declaration
869        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
870            symbols.push(Symbol {
871                name: node_text(source, &name_node).to_string(),
872                kind: SymbolKind::Class,
873                range: node_range_with_decorators(&def_node, source, lang),
874                signature: Some(extract_signature(source, &def_node)),
875                scope_chain: vec![],
876                exported: is_exported(&def_node, &export_ranges),
877                parent: None,
878            });
879        }
880
881        // Method definition
882        if let (Some(class_name_node), Some(name_node), Some(def_node)) =
883            (method_class_name_node, method_name_node, method_def_node)
884        {
885            let class_name = node_text(source, &class_name_node).to_string();
886            symbols.push(Symbol {
887                name: node_text(source, &name_node).to_string(),
888                kind: SymbolKind::Method,
889                range: node_range_with_decorators(&def_node, source, lang),
890                signature: Some(extract_signature(source, &def_node)),
891                scope_chain: vec![class_name.clone()],
892                exported: false, // methods inherit export from class
893                parent: Some(class_name),
894            });
895        }
896
897        // Interface declaration
898        if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
899            symbols.push(Symbol {
900                name: node_text(source, &name_node).to_string(),
901                kind: SymbolKind::Interface,
902                range: node_range_with_decorators(&def_node, source, lang),
903                signature: Some(extract_signature(source, &def_node)),
904                scope_chain: vec![],
905                exported: is_exported(&def_node, &export_ranges),
906                parent: None,
907            });
908        }
909
910        // Enum declaration
911        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
912            symbols.push(Symbol {
913                name: node_text(source, &name_node).to_string(),
914                kind: SymbolKind::Enum,
915                range: node_range_with_decorators(&def_node, source, lang),
916                signature: Some(extract_signature(source, &def_node)),
917                scope_chain: vec![],
918                exported: is_exported(&def_node, &export_ranges),
919                parent: None,
920            });
921        }
922
923        // Type alias
924        if let (Some(name_node), Some(def_node)) = (type_alias_name_node, type_alias_def_node) {
925            symbols.push(Symbol {
926                name: node_text(source, &name_node).to_string(),
927                kind: SymbolKind::TypeAlias,
928                range: node_range_with_decorators(&def_node, source, lang),
929                signature: Some(extract_signature(source, &def_node)),
930                scope_chain: vec![],
931                exported: is_exported(&def_node, &export_ranges),
932                parent: None,
933            });
934        }
935
936        // Top-level const/let variable declaration (not arrow functions — those are handled above)
937        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
938            // Only include module-scope variables (parent is program/export_statement, not inside a function)
939            let is_top_level = def_node
940                .parent()
941                .map(|p| p.kind() == "program" || p.kind() == "export_statement")
942                .unwrap_or(false);
943            let is_function_like = lexical_declaration_has_function_value(&def_node);
944            let name = node_text(source, &name_node).to_string();
945            let already_captured = symbols.iter().any(|s| s.name == name);
946            if is_top_level && !is_function_like && !already_captured {
947                symbols.push(Symbol {
948                    name,
949                    kind: SymbolKind::Variable,
950                    range: node_range_with_decorators(&def_node, source, lang),
951                    signature: Some(extract_signature(source, &def_node)),
952                    scope_chain: vec![],
953                    exported: is_exported(&def_node, &export_ranges),
954                    parent: None,
955                });
956            }
957        }
958    }
959
960    // Deduplicate: methods can appear as both class and method captures
961    dedup_symbols(&mut symbols);
962    Ok(symbols)
963}
964
965/// Extract symbols from JavaScript source.
966fn extract_js_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
967    let lang = LangId::JavaScript;
968    let capture_names = query.capture_names();
969
970    let export_ranges = collect_export_ranges(source, root, query);
971
972    let mut symbols = Vec::new();
973    let mut cursor = QueryCursor::new();
974    let mut matches = cursor.matches(query, *root, source.as_bytes());
975
976    while let Some(m) = {
977        matches.advance();
978        matches.get()
979    } {
980        let mut fn_name_node = None;
981        let mut fn_def_node = None;
982        let mut arrow_name_node = None;
983        let mut arrow_def_node = None;
984        let mut class_name_node = None;
985        let mut class_def_node = None;
986        let mut method_class_name_node = None;
987        let mut method_name_node = None;
988        let mut method_def_node = None;
989
990        for cap in m.captures {
991            let Some(&name) = capture_names.get(cap.index as usize) else {
992                continue;
993            };
994            match name {
995                "fn.name" => fn_name_node = Some(cap.node),
996                "fn.def" => fn_def_node = Some(cap.node),
997                "arrow.name" => arrow_name_node = Some(cap.node),
998                "arrow.def" => arrow_def_node = Some(cap.node),
999                "class.name" => class_name_node = Some(cap.node),
1000                "class.def" => class_def_node = Some(cap.node),
1001                "method.class_name" => method_class_name_node = Some(cap.node),
1002                "method.name" => method_name_node = Some(cap.node),
1003                "method.def" => method_def_node = Some(cap.node),
1004                _ => {}
1005            }
1006        }
1007
1008        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1009            symbols.push(Symbol {
1010                name: node_text(source, &name_node).to_string(),
1011                kind: SymbolKind::Function,
1012                range: node_range_with_decorators(&def_node, source, lang),
1013                signature: Some(extract_signature(source, &def_node)),
1014                scope_chain: vec![],
1015                exported: is_exported(&def_node, &export_ranges),
1016                parent: None,
1017            });
1018        }
1019
1020        if let (Some(name_node), Some(def_node)) = (arrow_name_node, arrow_def_node) {
1021            symbols.push(Symbol {
1022                name: node_text(source, &name_node).to_string(),
1023                kind: SymbolKind::Function,
1024                range: node_range_with_decorators(&def_node, source, lang),
1025                signature: Some(extract_signature(source, &def_node)),
1026                scope_chain: vec![],
1027                exported: is_exported(&def_node, &export_ranges),
1028                parent: None,
1029            });
1030        }
1031
1032        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
1033            symbols.push(Symbol {
1034                name: node_text(source, &name_node).to_string(),
1035                kind: SymbolKind::Class,
1036                range: node_range_with_decorators(&def_node, source, lang),
1037                signature: Some(extract_signature(source, &def_node)),
1038                scope_chain: vec![],
1039                exported: is_exported(&def_node, &export_ranges),
1040                parent: None,
1041            });
1042        }
1043
1044        if let (Some(class_name_node), Some(name_node), Some(def_node)) =
1045            (method_class_name_node, method_name_node, method_def_node)
1046        {
1047            let class_name = node_text(source, &class_name_node).to_string();
1048            symbols.push(Symbol {
1049                name: node_text(source, &name_node).to_string(),
1050                kind: SymbolKind::Method,
1051                range: node_range_with_decorators(&def_node, source, lang),
1052                signature: Some(extract_signature(source, &def_node)),
1053                scope_chain: vec![class_name.clone()],
1054                exported: false,
1055                parent: Some(class_name),
1056            });
1057        }
1058    }
1059
1060    dedup_symbols(&mut symbols);
1061    Ok(symbols)
1062}
1063
1064/// Walk parent nodes to build a scope chain for Python symbols.
1065/// A function inside `class_definition > block` gets the class name in its scope.
1066fn py_scope_chain(node: &Node, source: &str) -> Vec<String> {
1067    let mut chain = Vec::new();
1068    let mut current = node.parent();
1069    while let Some(parent) = current {
1070        if parent.kind() == "class_definition" {
1071            if let Some(name_node) = parent.child_by_field_name("name") {
1072                chain.push(node_text(source, &name_node).to_string());
1073            }
1074        }
1075        current = parent.parent();
1076    }
1077    chain.reverse();
1078    chain
1079}
1080
1081/// Extract symbols from Python source.
1082fn extract_py_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1083    let lang = LangId::Python;
1084    let capture_names = query.capture_names();
1085
1086    let mut symbols = Vec::new();
1087    let mut cursor = QueryCursor::new();
1088    let mut matches = cursor.matches(query, *root, source.as_bytes());
1089
1090    // Track decorated definitions to avoid double-counting
1091    let mut decorated_fn_lines = std::collections::HashSet::new();
1092
1093    // First pass: collect decorated definition info
1094    {
1095        let mut cursor2 = QueryCursor::new();
1096        let mut matches2 = cursor2.matches(query, *root, source.as_bytes());
1097        while let Some(m) = {
1098            matches2.advance();
1099            matches2.get()
1100        } {
1101            let mut dec_def_node = None;
1102            let mut dec_decorator_node = None;
1103
1104            for cap in m.captures {
1105                let Some(&name) = capture_names.get(cap.index as usize) else {
1106                    continue;
1107                };
1108                match name {
1109                    "dec.def" => dec_def_node = Some(cap.node),
1110                    "dec.decorator" => dec_decorator_node = Some(cap.node),
1111                    _ => {}
1112                }
1113            }
1114
1115            if let (Some(def_node), Some(_dec_node)) = (dec_def_node, dec_decorator_node) {
1116                // Find the inner function_definition or class_definition
1117                let mut child_cursor = def_node.walk();
1118                if child_cursor.goto_first_child() {
1119                    loop {
1120                        let child = child_cursor.node();
1121                        if child.kind() == "function_definition"
1122                            || child.kind() == "class_definition"
1123                        {
1124                            decorated_fn_lines.insert(child.start_position().row);
1125                        }
1126                        if !child_cursor.goto_next_sibling() {
1127                            break;
1128                        }
1129                    }
1130                }
1131            }
1132        }
1133    }
1134
1135    while let Some(m) = {
1136        matches.advance();
1137        matches.get()
1138    } {
1139        let mut fn_name_node = None;
1140        let mut fn_def_node = None;
1141        let mut class_name_node = None;
1142        let mut class_def_node = None;
1143
1144        for cap in m.captures {
1145            let Some(&name) = capture_names.get(cap.index as usize) else {
1146                continue;
1147            };
1148            match name {
1149                "fn.name" => fn_name_node = Some(cap.node),
1150                "fn.def" => fn_def_node = Some(cap.node),
1151                "class.name" => class_name_node = Some(cap.node),
1152                "class.def" => class_def_node = Some(cap.node),
1153                _ => {}
1154            }
1155        }
1156
1157        // Function definition
1158        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1159            let scope = py_scope_chain(&def_node, source);
1160            let is_method = !scope.is_empty();
1161            let name = node_text(source, &name_node).to_string();
1162            // Skip __init__ and other dunders as separate symbols — they're methods
1163            let kind = if is_method {
1164                SymbolKind::Method
1165            } else {
1166                SymbolKind::Function
1167            };
1168
1169            // Build signature — include decorator if this is a decorated function
1170            let sig = if decorated_fn_lines.contains(&def_node.start_position().row) {
1171                // Find the decorated_definition parent to get decorator text
1172                let mut sig_parts = Vec::new();
1173                let mut parent = def_node.parent();
1174                while let Some(p) = parent {
1175                    if p.kind() == "decorated_definition" {
1176                        // Get decorator lines
1177                        let mut dc = p.walk();
1178                        if dc.goto_first_child() {
1179                            loop {
1180                                if dc.node().kind() == "decorator" {
1181                                    sig_parts.push(node_text(source, &dc.node()).to_string());
1182                                }
1183                                if !dc.goto_next_sibling() {
1184                                    break;
1185                                }
1186                            }
1187                        }
1188                        break;
1189                    }
1190                    parent = p.parent();
1191                }
1192                sig_parts.push(extract_signature(source, &def_node));
1193                Some(sig_parts.join("\n"))
1194            } else {
1195                Some(extract_signature(source, &def_node))
1196            };
1197
1198            symbols.push(Symbol {
1199                name,
1200                kind,
1201                range: node_range_with_decorators(&def_node, source, lang),
1202                signature: sig,
1203                scope_chain: scope.clone(),
1204                exported: false, // Python has no export concept
1205                parent: scope.last().cloned(),
1206            });
1207        }
1208
1209        // Class definition
1210        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
1211            let scope = py_scope_chain(&def_node, source);
1212
1213            // Build signature — include decorator if decorated
1214            let sig = if decorated_fn_lines.contains(&def_node.start_position().row) {
1215                let mut sig_parts = Vec::new();
1216                let mut parent = def_node.parent();
1217                while let Some(p) = parent {
1218                    if p.kind() == "decorated_definition" {
1219                        let mut dc = p.walk();
1220                        if dc.goto_first_child() {
1221                            loop {
1222                                if dc.node().kind() == "decorator" {
1223                                    sig_parts.push(node_text(source, &dc.node()).to_string());
1224                                }
1225                                if !dc.goto_next_sibling() {
1226                                    break;
1227                                }
1228                            }
1229                        }
1230                        break;
1231                    }
1232                    parent = p.parent();
1233                }
1234                sig_parts.push(extract_signature(source, &def_node));
1235                Some(sig_parts.join("\n"))
1236            } else {
1237                Some(extract_signature(source, &def_node))
1238            };
1239
1240            symbols.push(Symbol {
1241                name: node_text(source, &name_node).to_string(),
1242                kind: SymbolKind::Class,
1243                range: node_range_with_decorators(&def_node, source, lang),
1244                signature: sig,
1245                scope_chain: scope.clone(),
1246                exported: false,
1247                parent: scope.last().cloned(),
1248            });
1249        }
1250    }
1251
1252    dedup_symbols(&mut symbols);
1253    Ok(symbols)
1254}
1255
1256/// Extract symbols from Rust source.
1257/// Handles: free functions, struct, enum, trait (as Interface), impl methods with scope chains.
1258fn extract_rs_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1259    let lang = LangId::Rust;
1260    let capture_names = query.capture_names();
1261
1262    // Collect all visibility_modifier byte ranges first
1263    let mut vis_ranges: Vec<std::ops::Range<usize>> = Vec::new();
1264    {
1265        let vis_idx = capture_names.iter().position(|n| *n == "vis.mod");
1266        if let Some(idx) = vis_idx {
1267            let idx = idx as u32;
1268            let mut cursor = QueryCursor::new();
1269            let mut matches = cursor.matches(query, *root, source.as_bytes());
1270            while let Some(m) = {
1271                matches.advance();
1272                matches.get()
1273            } {
1274                for cap in m.captures {
1275                    if cap.index == idx {
1276                        vis_ranges.push(cap.node.byte_range());
1277                    }
1278                }
1279            }
1280        }
1281    }
1282
1283    let is_pub = |node: &Node| -> bool {
1284        // Check if the node has a visibility_modifier as a direct child
1285        let mut child_cursor = node.walk();
1286        if child_cursor.goto_first_child() {
1287            loop {
1288                if child_cursor.node().kind() == "visibility_modifier" {
1289                    return true;
1290                }
1291                if !child_cursor.goto_next_sibling() {
1292                    break;
1293                }
1294            }
1295        }
1296        false
1297    };
1298
1299    let mut symbols = Vec::new();
1300    let mut cursor = QueryCursor::new();
1301    let mut matches = cursor.matches(query, *root, source.as_bytes());
1302
1303    while let Some(m) = {
1304        matches.advance();
1305        matches.get()
1306    } {
1307        let mut fn_name_node = None;
1308        let mut fn_def_node = None;
1309        let mut struct_name_node = None;
1310        let mut struct_def_node = None;
1311        let mut enum_name_node = None;
1312        let mut enum_def_node = None;
1313        let mut trait_name_node = None;
1314        let mut trait_def_node = None;
1315        let mut impl_def_node = None;
1316
1317        for cap in m.captures {
1318            let Some(&name) = capture_names.get(cap.index as usize) else {
1319                continue;
1320            };
1321            match name {
1322                "fn.name" => fn_name_node = Some(cap.node),
1323                "fn.def" => fn_def_node = Some(cap.node),
1324                "struct.name" => struct_name_node = Some(cap.node),
1325                "struct.def" => struct_def_node = Some(cap.node),
1326                "enum.name" => enum_name_node = Some(cap.node),
1327                "enum.def" => enum_def_node = Some(cap.node),
1328                "trait.name" => trait_name_node = Some(cap.node),
1329                "trait.def" => trait_def_node = Some(cap.node),
1330                "impl.def" => impl_def_node = Some(cap.node),
1331                _ => {}
1332            }
1333        }
1334
1335        // Free function (not inside impl block — check parent)
1336        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1337            let parent = def_node.parent();
1338            let in_impl = parent
1339                .map(|p| p.kind() == "declaration_list")
1340                .unwrap_or(false);
1341            if !in_impl {
1342                symbols.push(Symbol {
1343                    name: node_text(source, &name_node).to_string(),
1344                    kind: SymbolKind::Function,
1345                    range: node_range_with_decorators(&def_node, source, lang),
1346                    signature: Some(extract_signature(source, &def_node)),
1347                    scope_chain: vec![],
1348                    exported: is_pub(&def_node),
1349                    parent: None,
1350                });
1351            }
1352        }
1353
1354        // Struct
1355        if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
1356            symbols.push(Symbol {
1357                name: node_text(source, &name_node).to_string(),
1358                kind: SymbolKind::Struct,
1359                range: node_range_with_decorators(&def_node, source, lang),
1360                signature: Some(extract_signature(source, &def_node)),
1361                scope_chain: vec![],
1362                exported: is_pub(&def_node),
1363                parent: None,
1364            });
1365        }
1366
1367        // Enum
1368        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
1369            symbols.push(Symbol {
1370                name: node_text(source, &name_node).to_string(),
1371                kind: SymbolKind::Enum,
1372                range: node_range_with_decorators(&def_node, source, lang),
1373                signature: Some(extract_signature(source, &def_node)),
1374                scope_chain: vec![],
1375                exported: is_pub(&def_node),
1376                parent: None,
1377            });
1378        }
1379
1380        // Trait (mapped to Interface kind)
1381        if let (Some(name_node), Some(def_node)) = (trait_name_node, trait_def_node) {
1382            symbols.push(Symbol {
1383                name: node_text(source, &name_node).to_string(),
1384                kind: SymbolKind::Interface,
1385                range: node_range_with_decorators(&def_node, source, lang),
1386                signature: Some(extract_signature(source, &def_node)),
1387                scope_chain: vec![],
1388                exported: is_pub(&def_node),
1389                parent: None,
1390            });
1391        }
1392
1393        // Impl block — extract methods from inside
1394        if let Some(impl_node) = impl_def_node {
1395            // Find the type name(s) from the impl
1396            // `impl TypeName { ... }` → scope = ["TypeName"]
1397            // `impl Trait for TypeName { ... }` → scope = ["Trait for TypeName"]
1398            let mut type_names: Vec<String> = Vec::new();
1399            let mut child_cursor = impl_node.walk();
1400            if child_cursor.goto_first_child() {
1401                loop {
1402                    let child = child_cursor.node();
1403                    if child.kind() == "type_identifier" || child.kind() == "generic_type" {
1404                        type_names.push(node_text(source, &child).to_string());
1405                    }
1406                    if !child_cursor.goto_next_sibling() {
1407                        break;
1408                    }
1409                }
1410            }
1411
1412            let scope_name = if type_names.len() >= 2 {
1413                // impl Trait for Type
1414                format!("{} for {}", type_names[0], type_names[1])
1415            } else if type_names.len() == 1 {
1416                type_names[0].clone()
1417            } else {
1418                String::new()
1419            };
1420
1421            let parent_name = type_names.last().cloned().unwrap_or_default();
1422
1423            // Find declaration_list and extract function_items
1424            let mut child_cursor = impl_node.walk();
1425            if child_cursor.goto_first_child() {
1426                loop {
1427                    let child = child_cursor.node();
1428                    if child.kind() == "declaration_list" {
1429                        let mut fn_cursor = child.walk();
1430                        if fn_cursor.goto_first_child() {
1431                            loop {
1432                                let fn_node = fn_cursor.node();
1433                                if fn_node.kind() == "function_item" {
1434                                    if let Some(name_node) = fn_node.child_by_field_name("name") {
1435                                        symbols.push(Symbol {
1436                                            name: node_text(source, &name_node).to_string(),
1437                                            kind: SymbolKind::Method,
1438                                            range: node_range_with_decorators(
1439                                                &fn_node, source, lang,
1440                                            ),
1441                                            signature: Some(extract_signature(source, &fn_node)),
1442                                            scope_chain: if scope_name.is_empty() {
1443                                                vec![]
1444                                            } else {
1445                                                vec![scope_name.clone()]
1446                                            },
1447                                            exported: is_pub(&fn_node),
1448                                            parent: if parent_name.is_empty() {
1449                                                None
1450                                            } else {
1451                                                Some(parent_name.clone())
1452                                            },
1453                                        });
1454                                    }
1455                                }
1456                                if !fn_cursor.goto_next_sibling() {
1457                                    break;
1458                                }
1459                            }
1460                        }
1461                    }
1462                    if !child_cursor.goto_next_sibling() {
1463                        break;
1464                    }
1465                }
1466            }
1467        }
1468    }
1469
1470    dedup_symbols(&mut symbols);
1471    Ok(symbols)
1472}
1473
1474/// Extract symbols from Go source.
1475/// Handles: functions, methods (with receiver scope chain), struct/interface types,
1476/// uppercase-first-letter export detection.
1477fn extract_go_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1478    let lang = LangId::Go;
1479    let capture_names = query.capture_names();
1480
1481    let is_go_exported = |name: &str| -> bool {
1482        name.chars()
1483            .next()
1484            .map(|c| c.is_uppercase())
1485            .unwrap_or(false)
1486    };
1487
1488    let mut symbols = Vec::new();
1489    let mut cursor = QueryCursor::new();
1490    let mut matches = cursor.matches(query, *root, source.as_bytes());
1491
1492    while let Some(m) = {
1493        matches.advance();
1494        matches.get()
1495    } {
1496        let mut fn_name_node = None;
1497        let mut fn_def_node = None;
1498        let mut method_name_node = None;
1499        let mut method_def_node = None;
1500        let mut type_name_node = None;
1501        let mut type_body_node = None;
1502        let mut type_def_node = None;
1503
1504        for cap in m.captures {
1505            let Some(&name) = capture_names.get(cap.index as usize) else {
1506                continue;
1507            };
1508            match name {
1509                "fn.name" => fn_name_node = Some(cap.node),
1510                "fn.def" => fn_def_node = Some(cap.node),
1511                "method.name" => method_name_node = Some(cap.node),
1512                "method.def" => method_def_node = Some(cap.node),
1513                "type.name" => type_name_node = Some(cap.node),
1514                "type.body" => type_body_node = Some(cap.node),
1515                "type.def" => type_def_node = Some(cap.node),
1516                _ => {}
1517            }
1518        }
1519
1520        // Function declaration
1521        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1522            let name = node_text(source, &name_node).to_string();
1523            symbols.push(Symbol {
1524                exported: is_go_exported(&name),
1525                name,
1526                kind: SymbolKind::Function,
1527                range: node_range_with_decorators(&def_node, source, lang),
1528                signature: Some(extract_signature(source, &def_node)),
1529                scope_chain: vec![],
1530                parent: None,
1531            });
1532        }
1533
1534        // Method declaration (with receiver)
1535        if let (Some(name_node), Some(def_node)) = (method_name_node, method_def_node) {
1536            let name = node_text(source, &name_node).to_string();
1537
1538            // Extract receiver type from the first parameter_list
1539            let receiver_type = extract_go_receiver_type(&def_node, source);
1540            let scope_chain = if let Some(ref rt) = receiver_type {
1541                vec![rt.clone()]
1542            } else {
1543                vec![]
1544            };
1545
1546            symbols.push(Symbol {
1547                exported: is_go_exported(&name),
1548                name,
1549                kind: SymbolKind::Method,
1550                range: node_range_with_decorators(&def_node, source, lang),
1551                signature: Some(extract_signature(source, &def_node)),
1552                scope_chain,
1553                parent: receiver_type,
1554            });
1555        }
1556
1557        // Type declarations (struct or interface)
1558        if let (Some(name_node), Some(body_node), Some(def_node)) =
1559            (type_name_node, type_body_node, type_def_node)
1560        {
1561            let name = node_text(source, &name_node).to_string();
1562            let kind = match body_node.kind() {
1563                "struct_type" => SymbolKind::Struct,
1564                "interface_type" => SymbolKind::Interface,
1565                _ => SymbolKind::TypeAlias,
1566            };
1567
1568            symbols.push(Symbol {
1569                exported: is_go_exported(&name),
1570                name,
1571                kind,
1572                range: node_range_with_decorators(&def_node, source, lang),
1573                signature: Some(extract_signature(source, &def_node)),
1574                scope_chain: vec![],
1575                parent: None,
1576            });
1577        }
1578    }
1579
1580    dedup_symbols(&mut symbols);
1581    Ok(symbols)
1582}
1583
1584/// Extract the receiver type from a Go method_declaration node.
1585/// e.g. `func (m *MyStruct) String()` → Some("MyStruct")
1586fn extract_go_receiver_type(method_node: &Node, source: &str) -> Option<String> {
1587    // The first parameter_list is the receiver
1588    let mut child_cursor = method_node.walk();
1589    if child_cursor.goto_first_child() {
1590        loop {
1591            let child = child_cursor.node();
1592            if child.kind() == "parameter_list" {
1593                // Walk into parameter_list to find type_identifier
1594                return find_type_identifier_recursive(&child, source);
1595            }
1596            if !child_cursor.goto_next_sibling() {
1597                break;
1598            }
1599        }
1600    }
1601    None
1602}
1603
1604fn split_scope_text(text: &str, separator: &str) -> Vec<String> {
1605    text.split(separator)
1606        .map(str::trim)
1607        .filter(|segment| !segment.is_empty())
1608        .map(ToString::to_string)
1609        .collect()
1610}
1611
1612fn last_scope_segment(text: &str, separator: &str) -> String {
1613    split_scope_text(text, separator)
1614        .pop()
1615        .unwrap_or_else(|| text.trim().to_string())
1616}
1617
1618fn zig_container_scope_chain(node: &Node, source: &str) -> Vec<String> {
1619    let mut chain = Vec::new();
1620    let mut current = node.parent();
1621
1622    while let Some(parent) = current {
1623        if matches!(
1624            parent.kind(),
1625            "struct_declaration" | "enum_declaration" | "union_declaration" | "opaque_declaration"
1626        ) {
1627            if let Some(container) = parent.parent() {
1628                if container.kind() == "variable_declaration" {
1629                    let mut cursor = container.walk();
1630                    if cursor.goto_first_child() {
1631                        loop {
1632                            let child = cursor.node();
1633                            if child.kind() == "identifier" {
1634                                chain.push(node_text(source, &child).to_string());
1635                                break;
1636                            }
1637                            if !cursor.goto_next_sibling() {
1638                                break;
1639                            }
1640                        }
1641                    }
1642                }
1643            }
1644        }
1645        current = parent.parent();
1646    }
1647
1648    chain.reverse();
1649    chain
1650}
1651
1652fn csharp_scope_chain(node: &Node, source: &str) -> Vec<String> {
1653    let mut chain = Vec::new();
1654    let mut current = node.parent();
1655
1656    while let Some(parent) = current {
1657        match parent.kind() {
1658            "namespace_declaration" | "file_scoped_namespace_declaration" => {
1659                if let Some(name_node) = parent.child_by_field_name("name") {
1660                    chain.push(node_text(source, &name_node).to_string());
1661                }
1662            }
1663            "class_declaration"
1664            | "interface_declaration"
1665            | "struct_declaration"
1666            | "record_declaration" => {
1667                if let Some(name_node) = parent.child_by_field_name("name") {
1668                    chain.push(node_text(source, &name_node).to_string());
1669                }
1670            }
1671            _ => {}
1672        }
1673        current = parent.parent();
1674    }
1675
1676    chain.reverse();
1677    chain
1678}
1679
1680fn cpp_parent_scope_chain(node: &Node, source: &str) -> Vec<String> {
1681    let mut chain = Vec::new();
1682    let mut current = node.parent();
1683
1684    while let Some(parent) = current {
1685        match parent.kind() {
1686            "namespace_definition" => {
1687                if let Some(name_node) = parent.child_by_field_name("name") {
1688                    chain.push(node_text(source, &name_node).to_string());
1689                }
1690            }
1691            "class_specifier" | "struct_specifier" => {
1692                if let Some(name_node) = parent.child_by_field_name("name") {
1693                    chain.push(last_scope_segment(node_text(source, &name_node), "::"));
1694                }
1695            }
1696            _ => {}
1697        }
1698        current = parent.parent();
1699    }
1700
1701    chain.reverse();
1702    chain
1703}
1704
1705fn template_signature(source: &str, template_node: &Node, item_node: &Node) -> String {
1706    format!(
1707        "{}\n{}",
1708        extract_signature(source, template_node),
1709        extract_signature(source, item_node)
1710    )
1711}
1712
1713/// Extract symbols from C source.
1714fn extract_c_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1715    let lang = LangId::C;
1716    let capture_names = query.capture_names();
1717
1718    let mut symbols = Vec::new();
1719    let mut cursor = QueryCursor::new();
1720    let mut matches = cursor.matches(query, *root, source.as_bytes());
1721
1722    while let Some(m) = {
1723        matches.advance();
1724        matches.get()
1725    } {
1726        let mut fn_name_node = None;
1727        let mut fn_def_node = None;
1728        let mut struct_name_node = None;
1729        let mut struct_def_node = None;
1730        let mut enum_name_node = None;
1731        let mut enum_def_node = None;
1732        let mut type_name_node = None;
1733        let mut type_def_node = None;
1734        let mut macro_name_node = None;
1735        let mut macro_def_node = None;
1736
1737        for cap in m.captures {
1738            let Some(&name) = capture_names.get(cap.index as usize) else {
1739                continue;
1740            };
1741            match name {
1742                "fn.name" => fn_name_node = Some(cap.node),
1743                "fn.def" => fn_def_node = Some(cap.node),
1744                "struct.name" => struct_name_node = Some(cap.node),
1745                "struct.def" => struct_def_node = Some(cap.node),
1746                "enum.name" => enum_name_node = Some(cap.node),
1747                "enum.def" => enum_def_node = Some(cap.node),
1748                "type.name" => type_name_node = Some(cap.node),
1749                "type.def" => type_def_node = Some(cap.node),
1750                "macro.name" => macro_name_node = Some(cap.node),
1751                "macro.def" => macro_def_node = Some(cap.node),
1752                _ => {}
1753            }
1754        }
1755
1756        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1757            symbols.push(Symbol {
1758                name: node_text(source, &name_node).to_string(),
1759                kind: SymbolKind::Function,
1760                range: node_range_with_decorators(&def_node, source, lang),
1761                signature: Some(extract_signature(source, &def_node)),
1762                scope_chain: vec![],
1763                exported: false,
1764                parent: None,
1765            });
1766        }
1767
1768        if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
1769            symbols.push(Symbol {
1770                name: node_text(source, &name_node).to_string(),
1771                kind: SymbolKind::Struct,
1772                range: node_range_with_decorators(&def_node, source, lang),
1773                signature: Some(extract_signature(source, &def_node)),
1774                scope_chain: vec![],
1775                exported: false,
1776                parent: None,
1777            });
1778        }
1779
1780        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
1781            symbols.push(Symbol {
1782                name: node_text(source, &name_node).to_string(),
1783                kind: SymbolKind::Enum,
1784                range: node_range_with_decorators(&def_node, source, lang),
1785                signature: Some(extract_signature(source, &def_node)),
1786                scope_chain: vec![],
1787                exported: false,
1788                parent: None,
1789            });
1790        }
1791
1792        if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
1793            symbols.push(Symbol {
1794                name: node_text(source, &name_node).to_string(),
1795                kind: SymbolKind::TypeAlias,
1796                range: node_range_with_decorators(&def_node, source, lang),
1797                signature: Some(extract_signature(source, &def_node)),
1798                scope_chain: vec![],
1799                exported: false,
1800                parent: None,
1801            });
1802        }
1803
1804        if let (Some(name_node), Some(def_node)) = (macro_name_node, macro_def_node) {
1805            symbols.push(Symbol {
1806                name: node_text(source, &name_node).to_string(),
1807                kind: SymbolKind::Variable,
1808                range: node_range(&def_node),
1809                signature: Some(extract_signature(source, &def_node)),
1810                scope_chain: vec![],
1811                exported: false,
1812                parent: None,
1813            });
1814        }
1815    }
1816
1817    dedup_symbols(&mut symbols);
1818    Ok(symbols)
1819}
1820
1821/// Extract symbols from C++ source.
1822fn extract_cpp_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1823    let lang = LangId::Cpp;
1824    let capture_names = query.capture_names();
1825
1826    let mut type_names = HashSet::new();
1827    {
1828        let mut cursor = QueryCursor::new();
1829        let mut matches = cursor.matches(query, *root, source.as_bytes());
1830        while let Some(m) = {
1831            matches.advance();
1832            matches.get()
1833        } {
1834            for cap in m.captures {
1835                let Some(&name) = capture_names.get(cap.index as usize) else {
1836                    continue;
1837                };
1838                match name {
1839                    "class.name"
1840                    | "struct.name"
1841                    | "template.class.name"
1842                    | "template.struct.name" => {
1843                        type_names.insert(last_scope_segment(node_text(source, &cap.node), "::"));
1844                    }
1845                    _ => {}
1846                }
1847            }
1848        }
1849    }
1850
1851    let mut symbols = Vec::new();
1852    let mut cursor = QueryCursor::new();
1853    let mut matches = cursor.matches(query, *root, source.as_bytes());
1854
1855    while let Some(m) = {
1856        matches.advance();
1857        matches.get()
1858    } {
1859        let mut fn_name_node = None;
1860        let mut fn_def_node = None;
1861        let mut method_name_node = None;
1862        let mut method_def_node = None;
1863        let mut qual_scope_node = None;
1864        let mut qual_name_node = None;
1865        let mut qual_def_node = None;
1866        let mut class_name_node = None;
1867        let mut class_def_node = None;
1868        let mut struct_name_node = None;
1869        let mut struct_def_node = None;
1870        let mut enum_name_node = None;
1871        let mut enum_def_node = None;
1872        let mut namespace_name_node = None;
1873        let mut namespace_def_node = None;
1874        let mut template_class_name_node = None;
1875        let mut template_class_def_node = None;
1876        let mut template_class_item_node = None;
1877        let mut template_struct_name_node = None;
1878        let mut template_struct_def_node = None;
1879        let mut template_struct_item_node = None;
1880        let mut template_fn_name_node = None;
1881        let mut template_fn_def_node = None;
1882        let mut template_fn_item_node = None;
1883        let mut template_qual_scope_node = None;
1884        let mut template_qual_name_node = None;
1885        let mut template_qual_def_node = None;
1886        let mut template_qual_item_node = None;
1887
1888        for cap in m.captures {
1889            let Some(&name) = capture_names.get(cap.index as usize) else {
1890                continue;
1891            };
1892            match name {
1893                "fn.name" => fn_name_node = Some(cap.node),
1894                "fn.def" => fn_def_node = Some(cap.node),
1895                "method.name" => method_name_node = Some(cap.node),
1896                "method.def" => method_def_node = Some(cap.node),
1897                "qual.scope" => qual_scope_node = Some(cap.node),
1898                "qual.name" => qual_name_node = Some(cap.node),
1899                "qual.def" => qual_def_node = Some(cap.node),
1900                "class.name" => class_name_node = Some(cap.node),
1901                "class.def" => class_def_node = Some(cap.node),
1902                "struct.name" => struct_name_node = Some(cap.node),
1903                "struct.def" => struct_def_node = Some(cap.node),
1904                "enum.name" => enum_name_node = Some(cap.node),
1905                "enum.def" => enum_def_node = Some(cap.node),
1906                "namespace.name" => namespace_name_node = Some(cap.node),
1907                "namespace.def" => namespace_def_node = Some(cap.node),
1908                "template.class.name" => template_class_name_node = Some(cap.node),
1909                "template.class.def" => template_class_def_node = Some(cap.node),
1910                "template.class.item" => template_class_item_node = Some(cap.node),
1911                "template.struct.name" => template_struct_name_node = Some(cap.node),
1912                "template.struct.def" => template_struct_def_node = Some(cap.node),
1913                "template.struct.item" => template_struct_item_node = Some(cap.node),
1914                "template.fn.name" => template_fn_name_node = Some(cap.node),
1915                "template.fn.def" => template_fn_def_node = Some(cap.node),
1916                "template.fn.item" => template_fn_item_node = Some(cap.node),
1917                "template.qual.scope" => template_qual_scope_node = Some(cap.node),
1918                "template.qual.name" => template_qual_name_node = Some(cap.node),
1919                "template.qual.def" => template_qual_def_node = Some(cap.node),
1920                "template.qual.item" => template_qual_item_node = Some(cap.node),
1921                _ => {}
1922            }
1923        }
1924
1925        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1926            let in_template = def_node
1927                .parent()
1928                .map(|parent| parent.kind() == "template_declaration")
1929                .unwrap_or(false);
1930            if !in_template {
1931                let scope_chain = cpp_parent_scope_chain(&def_node, source);
1932                symbols.push(Symbol {
1933                    name: node_text(source, &name_node).to_string(),
1934                    kind: SymbolKind::Function,
1935                    range: node_range_with_decorators(&def_node, source, lang),
1936                    signature: Some(extract_signature(source, &def_node)),
1937                    scope_chain: scope_chain.clone(),
1938                    exported: false,
1939                    parent: scope_chain.last().cloned(),
1940                });
1941            }
1942        }
1943
1944        if let (Some(name_node), Some(def_node)) = (method_name_node, method_def_node) {
1945            let scope_chain = cpp_parent_scope_chain(&def_node, source);
1946            symbols.push(Symbol {
1947                name: node_text(source, &name_node).to_string(),
1948                kind: SymbolKind::Method,
1949                range: node_range_with_decorators(&def_node, source, lang),
1950                signature: Some(extract_signature(source, &def_node)),
1951                scope_chain: scope_chain.clone(),
1952                exported: false,
1953                parent: scope_chain.last().cloned(),
1954            });
1955        }
1956
1957        if let (Some(scope_node), Some(name_node), Some(def_node)) =
1958            (qual_scope_node, qual_name_node, qual_def_node)
1959        {
1960            let in_template = def_node
1961                .parent()
1962                .map(|parent| parent.kind() == "template_declaration")
1963                .unwrap_or(false);
1964            if !in_template {
1965                let scope_text = node_text(source, &scope_node);
1966                let scope_chain = split_scope_text(scope_text, "::");
1967                let parent = scope_chain.last().cloned();
1968                let kind = if parent
1969                    .as_ref()
1970                    .map(|segment| type_names.contains(segment))
1971                    .unwrap_or(false)
1972                {
1973                    SymbolKind::Method
1974                } else {
1975                    SymbolKind::Function
1976                };
1977
1978                symbols.push(Symbol {
1979                    name: node_text(source, &name_node).to_string(),
1980                    kind,
1981                    range: node_range_with_decorators(&def_node, source, lang),
1982                    signature: Some(extract_signature(source, &def_node)),
1983                    scope_chain,
1984                    exported: false,
1985                    parent,
1986                });
1987            }
1988        }
1989
1990        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
1991            let in_template = def_node
1992                .parent()
1993                .map(|parent| parent.kind() == "template_declaration")
1994                .unwrap_or(false);
1995            if !in_template {
1996                let scope_chain = cpp_parent_scope_chain(&def_node, source);
1997                let name = last_scope_segment(node_text(source, &name_node), "::");
1998                symbols.push(Symbol {
1999                    name: name.clone(),
2000                    kind: SymbolKind::Class,
2001                    range: node_range_with_decorators(&def_node, source, lang),
2002                    signature: Some(extract_signature(source, &def_node)),
2003                    scope_chain: scope_chain.clone(),
2004                    exported: false,
2005                    parent: scope_chain.last().cloned(),
2006                });
2007            }
2008        }
2009
2010        if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
2011            let in_template = def_node
2012                .parent()
2013                .map(|parent| parent.kind() == "template_declaration")
2014                .unwrap_or(false);
2015            if !in_template {
2016                let scope_chain = cpp_parent_scope_chain(&def_node, source);
2017                let name = last_scope_segment(node_text(source, &name_node), "::");
2018                symbols.push(Symbol {
2019                    name: name.clone(),
2020                    kind: SymbolKind::Struct,
2021                    range: node_range_with_decorators(&def_node, source, lang),
2022                    signature: Some(extract_signature(source, &def_node)),
2023                    scope_chain: scope_chain.clone(),
2024                    exported: false,
2025                    parent: scope_chain.last().cloned(),
2026                });
2027            }
2028        }
2029
2030        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
2031            let scope_chain = cpp_parent_scope_chain(&def_node, source);
2032            let name = last_scope_segment(node_text(source, &name_node), "::");
2033            symbols.push(Symbol {
2034                name: name.clone(),
2035                kind: SymbolKind::Enum,
2036                range: node_range_with_decorators(&def_node, source, lang),
2037                signature: Some(extract_signature(source, &def_node)),
2038                scope_chain: scope_chain.clone(),
2039                exported: false,
2040                parent: scope_chain.last().cloned(),
2041            });
2042        }
2043
2044        if let (Some(name_node), Some(def_node)) = (namespace_name_node, namespace_def_node) {
2045            let scope_chain = cpp_parent_scope_chain(&def_node, source);
2046            symbols.push(Symbol {
2047                name: node_text(source, &name_node).to_string(),
2048                kind: SymbolKind::TypeAlias,
2049                range: node_range_with_decorators(&def_node, source, lang),
2050                signature: Some(extract_signature(source, &def_node)),
2051                scope_chain: scope_chain.clone(),
2052                exported: false,
2053                parent: scope_chain.last().cloned(),
2054            });
2055        }
2056
2057        if let (Some(name_node), Some(def_node), Some(item_node)) = (
2058            template_class_name_node,
2059            template_class_def_node,
2060            template_class_item_node,
2061        ) {
2062            let scope_chain = cpp_parent_scope_chain(&def_node, source);
2063            let name = last_scope_segment(node_text(source, &name_node), "::");
2064            symbols.push(Symbol {
2065                name: name.clone(),
2066                kind: SymbolKind::Class,
2067                range: node_range_with_decorators(&def_node, source, lang),
2068                signature: Some(template_signature(source, &def_node, &item_node)),
2069                scope_chain: scope_chain.clone(),
2070                exported: false,
2071                parent: scope_chain.last().cloned(),
2072            });
2073        }
2074
2075        if let (Some(name_node), Some(def_node), Some(item_node)) = (
2076            template_struct_name_node,
2077            template_struct_def_node,
2078            template_struct_item_node,
2079        ) {
2080            let scope_chain = cpp_parent_scope_chain(&def_node, source);
2081            let name = last_scope_segment(node_text(source, &name_node), "::");
2082            symbols.push(Symbol {
2083                name: name.clone(),
2084                kind: SymbolKind::Struct,
2085                range: node_range_with_decorators(&def_node, source, lang),
2086                signature: Some(template_signature(source, &def_node, &item_node)),
2087                scope_chain: scope_chain.clone(),
2088                exported: false,
2089                parent: scope_chain.last().cloned(),
2090            });
2091        }
2092
2093        if let (Some(name_node), Some(def_node), Some(item_node)) = (
2094            template_fn_name_node,
2095            template_fn_def_node,
2096            template_fn_item_node,
2097        ) {
2098            let scope_chain = cpp_parent_scope_chain(&def_node, source);
2099            symbols.push(Symbol {
2100                name: node_text(source, &name_node).to_string(),
2101                kind: SymbolKind::Function,
2102                range: node_range_with_decorators(&def_node, source, lang),
2103                signature: Some(template_signature(source, &def_node, &item_node)),
2104                scope_chain: scope_chain.clone(),
2105                exported: false,
2106                parent: scope_chain.last().cloned(),
2107            });
2108        }
2109
2110        if let (Some(scope_node), Some(name_node), Some(def_node), Some(item_node)) = (
2111            template_qual_scope_node,
2112            template_qual_name_node,
2113            template_qual_def_node,
2114            template_qual_item_node,
2115        ) {
2116            let scope_chain = split_scope_text(node_text(source, &scope_node), "::");
2117            let parent = scope_chain.last().cloned();
2118            let kind = if parent
2119                .as_ref()
2120                .map(|segment| type_names.contains(segment))
2121                .unwrap_or(false)
2122            {
2123                SymbolKind::Method
2124            } else {
2125                SymbolKind::Function
2126            };
2127
2128            symbols.push(Symbol {
2129                name: node_text(source, &name_node).to_string(),
2130                kind,
2131                range: node_range_with_decorators(&def_node, source, lang),
2132                signature: Some(template_signature(source, &def_node, &item_node)),
2133                scope_chain,
2134                exported: false,
2135                parent,
2136            });
2137        }
2138    }
2139
2140    dedup_symbols(&mut symbols);
2141    Ok(symbols)
2142}
2143
2144/// Extract symbols from Zig source.
2145fn extract_zig_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2146    let lang = LangId::Zig;
2147    let capture_names = query.capture_names();
2148
2149    let mut symbols = Vec::new();
2150    let mut cursor = QueryCursor::new();
2151    let mut matches = cursor.matches(query, *root, source.as_bytes());
2152
2153    while let Some(m) = {
2154        matches.advance();
2155        matches.get()
2156    } {
2157        let mut fn_name_node = None;
2158        let mut fn_def_node = None;
2159        let mut struct_name_node = None;
2160        let mut struct_def_node = None;
2161        let mut enum_name_node = None;
2162        let mut enum_def_node = None;
2163        let mut union_name_node = None;
2164        let mut union_def_node = None;
2165        let mut const_name_node = None;
2166        let mut const_def_node = None;
2167        let mut test_name_node = None;
2168        let mut test_def_node = None;
2169
2170        for cap in m.captures {
2171            let Some(&name) = capture_names.get(cap.index as usize) else {
2172                continue;
2173            };
2174            match name {
2175                "fn.name" => fn_name_node = Some(cap.node),
2176                "fn.def" => fn_def_node = Some(cap.node),
2177                "struct.name" => struct_name_node = Some(cap.node),
2178                "struct.def" => struct_def_node = Some(cap.node),
2179                "enum.name" => enum_name_node = Some(cap.node),
2180                "enum.def" => enum_def_node = Some(cap.node),
2181                "union.name" => union_name_node = Some(cap.node),
2182                "union.def" => union_def_node = Some(cap.node),
2183                "const.name" => const_name_node = Some(cap.node),
2184                "const.def" => const_def_node = Some(cap.node),
2185                "test.name" => test_name_node = Some(cap.node),
2186                "test.def" => test_def_node = Some(cap.node),
2187                _ => {}
2188            }
2189        }
2190
2191        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2192            let scope_chain = zig_container_scope_chain(&def_node, source);
2193            let kind = if scope_chain.is_empty() {
2194                SymbolKind::Function
2195            } else {
2196                SymbolKind::Method
2197            };
2198            symbols.push(Symbol {
2199                name: node_text(source, &name_node).to_string(),
2200                kind,
2201                range: node_range_with_decorators(&def_node, source, lang),
2202                signature: Some(extract_signature(source, &def_node)),
2203                scope_chain: scope_chain.clone(),
2204                exported: false,
2205                parent: scope_chain.last().cloned(),
2206            });
2207        }
2208
2209        if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
2210            symbols.push(Symbol {
2211                name: node_text(source, &name_node).to_string(),
2212                kind: SymbolKind::Struct,
2213                range: node_range_with_decorators(&def_node, source, lang),
2214                signature: Some(extract_signature(source, &def_node)),
2215                scope_chain: vec![],
2216                exported: false,
2217                parent: None,
2218            });
2219        }
2220
2221        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
2222            symbols.push(Symbol {
2223                name: node_text(source, &name_node).to_string(),
2224                kind: SymbolKind::Enum,
2225                range: node_range_with_decorators(&def_node, source, lang),
2226                signature: Some(extract_signature(source, &def_node)),
2227                scope_chain: vec![],
2228                exported: false,
2229                parent: None,
2230            });
2231        }
2232
2233        if let (Some(name_node), Some(def_node)) = (union_name_node, union_def_node) {
2234            symbols.push(Symbol {
2235                name: node_text(source, &name_node).to_string(),
2236                kind: SymbolKind::TypeAlias,
2237                range: node_range_with_decorators(&def_node, source, lang),
2238                signature: Some(extract_signature(source, &def_node)),
2239                scope_chain: vec![],
2240                exported: false,
2241                parent: None,
2242            });
2243        }
2244
2245        if let (Some(name_node), Some(def_node)) = (const_name_node, const_def_node) {
2246            let signature = extract_signature(source, &def_node);
2247            let is_container = signature.contains("= struct")
2248                || signature.contains("= enum")
2249                || signature.contains("= union")
2250                || signature.contains("= opaque");
2251            let is_const = signature.trim_start().starts_with("const ");
2252            let name = node_text(source, &name_node).to_string();
2253            let already_captured = symbols.iter().any(|symbol| symbol.name == name);
2254            if is_const && !is_container && !already_captured {
2255                symbols.push(Symbol {
2256                    name,
2257                    kind: SymbolKind::Variable,
2258                    range: node_range_with_decorators(&def_node, source, lang),
2259                    signature: Some(signature),
2260                    scope_chain: vec![],
2261                    exported: false,
2262                    parent: None,
2263                });
2264            }
2265        }
2266
2267        if let (Some(name_node), Some(def_node)) = (test_name_node, test_def_node) {
2268            let scope_chain = zig_container_scope_chain(&def_node, source);
2269            symbols.push(Symbol {
2270                name: node_text(source, &name_node).trim_matches('"').to_string(),
2271                kind: SymbolKind::Function,
2272                range: node_range_with_decorators(&def_node, source, lang),
2273                signature: Some(extract_signature(source, &def_node)),
2274                scope_chain: scope_chain.clone(),
2275                exported: false,
2276                parent: scope_chain.last().cloned(),
2277            });
2278        }
2279    }
2280
2281    dedup_symbols(&mut symbols);
2282    Ok(symbols)
2283}
2284
2285/// Extract symbols from C# source.
2286fn extract_csharp_symbols(
2287    source: &str,
2288    root: &Node,
2289    query: &Query,
2290) -> Result<Vec<Symbol>, AftError> {
2291    let lang = LangId::CSharp;
2292    let capture_names = query.capture_names();
2293
2294    let mut symbols = Vec::new();
2295    let mut cursor = QueryCursor::new();
2296    let mut matches = cursor.matches(query, *root, source.as_bytes());
2297
2298    while let Some(m) = {
2299        matches.advance();
2300        matches.get()
2301    } {
2302        let mut class_name_node = None;
2303        let mut class_def_node = None;
2304        let mut interface_name_node = None;
2305        let mut interface_def_node = None;
2306        let mut struct_name_node = None;
2307        let mut struct_def_node = None;
2308        let mut enum_name_node = None;
2309        let mut enum_def_node = None;
2310        let mut method_name_node = None;
2311        let mut method_def_node = None;
2312        let mut property_name_node = None;
2313        let mut property_def_node = None;
2314        let mut namespace_name_node = None;
2315        let mut namespace_def_node = None;
2316
2317        for cap in m.captures {
2318            let Some(&name) = capture_names.get(cap.index as usize) else {
2319                continue;
2320            };
2321            match name {
2322                "class.name" => class_name_node = Some(cap.node),
2323                "class.def" => class_def_node = Some(cap.node),
2324                "interface.name" => interface_name_node = Some(cap.node),
2325                "interface.def" => interface_def_node = Some(cap.node),
2326                "struct.name" => struct_name_node = Some(cap.node),
2327                "struct.def" => struct_def_node = Some(cap.node),
2328                "enum.name" => enum_name_node = Some(cap.node),
2329                "enum.def" => enum_def_node = Some(cap.node),
2330                "method.name" => method_name_node = Some(cap.node),
2331                "method.def" => method_def_node = Some(cap.node),
2332                "property.name" => property_name_node = Some(cap.node),
2333                "property.def" => property_def_node = Some(cap.node),
2334                "namespace.name" => namespace_name_node = Some(cap.node),
2335                "namespace.def" => namespace_def_node = Some(cap.node),
2336                _ => {}
2337            }
2338        }
2339
2340        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
2341            let scope_chain = csharp_scope_chain(&def_node, source);
2342            symbols.push(Symbol {
2343                name: node_text(source, &name_node).to_string(),
2344                kind: SymbolKind::Class,
2345                range: node_range_with_decorators(&def_node, source, lang),
2346                signature: Some(extract_signature(source, &def_node)),
2347                scope_chain: scope_chain.clone(),
2348                exported: false,
2349                parent: scope_chain.last().cloned(),
2350            });
2351        }
2352
2353        if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
2354            let scope_chain = csharp_scope_chain(&def_node, source);
2355            symbols.push(Symbol {
2356                name: node_text(source, &name_node).to_string(),
2357                kind: SymbolKind::Interface,
2358                range: node_range_with_decorators(&def_node, source, lang),
2359                signature: Some(extract_signature(source, &def_node)),
2360                scope_chain: scope_chain.clone(),
2361                exported: false,
2362                parent: scope_chain.last().cloned(),
2363            });
2364        }
2365
2366        if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
2367            let scope_chain = csharp_scope_chain(&def_node, source);
2368            symbols.push(Symbol {
2369                name: node_text(source, &name_node).to_string(),
2370                kind: SymbolKind::Struct,
2371                range: node_range_with_decorators(&def_node, source, lang),
2372                signature: Some(extract_signature(source, &def_node)),
2373                scope_chain: scope_chain.clone(),
2374                exported: false,
2375                parent: scope_chain.last().cloned(),
2376            });
2377        }
2378
2379        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
2380            let scope_chain = csharp_scope_chain(&def_node, source);
2381            symbols.push(Symbol {
2382                name: node_text(source, &name_node).to_string(),
2383                kind: SymbolKind::Enum,
2384                range: node_range_with_decorators(&def_node, source, lang),
2385                signature: Some(extract_signature(source, &def_node)),
2386                scope_chain: scope_chain.clone(),
2387                exported: false,
2388                parent: scope_chain.last().cloned(),
2389            });
2390        }
2391
2392        if let (Some(name_node), Some(def_node)) = (method_name_node, method_def_node) {
2393            let scope_chain = csharp_scope_chain(&def_node, source);
2394            symbols.push(Symbol {
2395                name: node_text(source, &name_node).to_string(),
2396                kind: SymbolKind::Method,
2397                range: node_range_with_decorators(&def_node, source, lang),
2398                signature: Some(extract_signature(source, &def_node)),
2399                scope_chain: scope_chain.clone(),
2400                exported: false,
2401                parent: scope_chain.last().cloned(),
2402            });
2403        }
2404
2405        if let (Some(name_node), Some(def_node)) = (property_name_node, property_def_node) {
2406            let scope_chain = csharp_scope_chain(&def_node, source);
2407            symbols.push(Symbol {
2408                name: node_text(source, &name_node).to_string(),
2409                kind: SymbolKind::Variable,
2410                range: node_range_with_decorators(&def_node, source, lang),
2411                signature: Some(extract_signature(source, &def_node)),
2412                scope_chain: scope_chain.clone(),
2413                exported: false,
2414                parent: scope_chain.last().cloned(),
2415            });
2416        }
2417
2418        if let (Some(name_node), Some(def_node)) = (namespace_name_node, namespace_def_node) {
2419            let scope_chain = csharp_scope_chain(&def_node, source);
2420            symbols.push(Symbol {
2421                name: node_text(source, &name_node).to_string(),
2422                kind: SymbolKind::TypeAlias,
2423                range: node_range_with_decorators(&def_node, source, lang),
2424                signature: Some(extract_signature(source, &def_node)),
2425                scope_chain: scope_chain.clone(),
2426                exported: false,
2427                parent: scope_chain.last().cloned(),
2428            });
2429        }
2430    }
2431
2432    dedup_symbols(&mut symbols);
2433    Ok(symbols)
2434}
2435
2436/// Recursively find the first type_identifier node in a subtree.
2437fn find_type_identifier_recursive(node: &Node, source: &str) -> Option<String> {
2438    if node.kind() == "type_identifier" {
2439        return Some(node_text(source, node).to_string());
2440    }
2441    let mut cursor = node.walk();
2442    if cursor.goto_first_child() {
2443        loop {
2444            if let Some(result) = find_type_identifier_recursive(&cursor.node(), source) {
2445                return Some(result);
2446            }
2447            if !cursor.goto_next_sibling() {
2448                break;
2449            }
2450        }
2451    }
2452    None
2453}
2454
2455/// Extract HTML headings (h1-h6) as symbols.
2456/// Each heading becomes a symbol with kind `Heading`, and its range covers
2457/// the element itself. Headings are nested based on their level.
2458fn extract_html_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
2459    let mut headings: Vec<(u8, Symbol)> = Vec::new();
2460    collect_html_headings(source, root, &mut headings);
2461
2462    // Build hierarchy: assign scope_chain and parent based on heading level
2463    let mut scope_stack: Vec<(u8, String)> = Vec::new(); // (level, name)
2464    for (level, symbol) in headings.iter_mut() {
2465        // Pop scope entries that are at the same level or deeper
2466        while scope_stack.last().is_some_and(|(l, _)| *l >= *level) {
2467            scope_stack.pop();
2468        }
2469        symbol.scope_chain = scope_stack.iter().map(|(_, name)| name.clone()).collect();
2470        symbol.parent = scope_stack.last().map(|(_, name)| name.clone());
2471        scope_stack.push((*level, symbol.name.clone()));
2472    }
2473
2474    Ok(headings.into_iter().map(|(_, s)| s).collect())
2475}
2476
2477/// Recursively collect h1-h6 elements from the HTML tree.
2478fn collect_html_headings(source: &str, node: &Node, headings: &mut Vec<(u8, Symbol)>) {
2479    let mut cursor = node.walk();
2480    if !cursor.goto_first_child() {
2481        return;
2482    }
2483
2484    loop {
2485        let child = cursor.node();
2486        if child.kind() == "element" {
2487            // Check if this element's start tag is h1-h6
2488            if let Some(start_tag) = child
2489                .child_by_field_name("start_tag")
2490                .or_else(|| child.child(0).filter(|c| c.kind() == "start_tag"))
2491            {
2492                if let Some(tag_name_node) = start_tag
2493                    .child_by_field_name("tag_name")
2494                    .or_else(|| start_tag.child(1).filter(|c| c.kind() == "tag_name"))
2495                {
2496                    let tag_name = node_text(source, &tag_name_node).to_lowercase();
2497                    if let Some(level) = match tag_name.as_str() {
2498                        "h1" => Some(1u8),
2499                        "h2" => Some(2),
2500                        "h3" => Some(3),
2501                        "h4" => Some(4),
2502                        "h5" => Some(5),
2503                        "h6" => Some(6),
2504                        _ => None,
2505                    } {
2506                        // Extract text content from the element
2507                        let text = extract_element_text(source, &child).trim().to_string();
2508                        if !text.is_empty() {
2509                            let range = node_range(&child);
2510                            let signature = format!("<h{}> {}", level, text);
2511                            headings.push((
2512                                level,
2513                                Symbol {
2514                                    name: text,
2515                                    kind: SymbolKind::Heading,
2516                                    range,
2517                                    signature: Some(signature),
2518                                    scope_chain: vec![], // filled later
2519                                    exported: false,
2520                                    parent: None, // filled later
2521                                },
2522                            ));
2523                        }
2524                    }
2525                }
2526            }
2527            // Recurse into element children (nested headings)
2528            collect_html_headings(source, &child, headings);
2529        } else {
2530            // Recurse into other node types (document, body, etc.)
2531            collect_html_headings(source, &child, headings);
2532        }
2533
2534        if !cursor.goto_next_sibling() {
2535            break;
2536        }
2537    }
2538}
2539
2540/// Extract text content from an HTML element, stripping tags.
2541fn extract_element_text(source: &str, node: &Node) -> String {
2542    let mut text = String::new();
2543    let mut cursor = node.walk();
2544    if !cursor.goto_first_child() {
2545        return text;
2546    }
2547    loop {
2548        let child = cursor.node();
2549        match child.kind() {
2550            "text" => {
2551                text.push_str(node_text(source, &child));
2552            }
2553            "element" => {
2554                // Recurse into nested elements (e.g., <strong>, <em>, <a>)
2555                text.push_str(&extract_element_text(source, &child));
2556            }
2557            _ => {}
2558        }
2559        if !cursor.goto_next_sibling() {
2560            break;
2561        }
2562    }
2563    text
2564}
2565
2566/// Extract markdown headings as symbols.
2567/// Each heading becomes a symbol with kind `Heading`, and its range covers the entire
2568/// section (from the heading to the next heading at the same or higher level, or EOF).
2569fn extract_md_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
2570    let mut symbols = Vec::new();
2571    extract_md_sections(source, root, &mut symbols, &[]);
2572    Ok(symbols)
2573}
2574
2575/// Recursively walk `section` nodes to build the heading hierarchy.
2576fn extract_md_sections(
2577    source: &str,
2578    node: &Node,
2579    symbols: &mut Vec<Symbol>,
2580    scope_chain: &[String],
2581) {
2582    let mut cursor = node.walk();
2583    if !cursor.goto_first_child() {
2584        return;
2585    }
2586
2587    loop {
2588        let child = cursor.node();
2589        match child.kind() {
2590            "section" => {
2591                // A section contains an atx_heading as its first child,
2592                // followed by content and possibly nested sections.
2593                let mut section_cursor = child.walk();
2594                let mut heading_name = String::new();
2595                let mut heading_level: u8 = 0;
2596
2597                if section_cursor.goto_first_child() {
2598                    loop {
2599                        let section_child = section_cursor.node();
2600                        if section_child.kind() == "atx_heading" {
2601                            // Extract heading level from marker type
2602                            let mut h_cursor = section_child.walk();
2603                            if h_cursor.goto_first_child() {
2604                                loop {
2605                                    let h_child = h_cursor.node();
2606                                    let kind = h_child.kind();
2607                                    if kind.starts_with("atx_h") && kind.ends_with("_marker") {
2608                                        // "atx_h1_marker" → level 1, "atx_h2_marker" → level 2, etc.
2609                                        heading_level = kind
2610                                            .strip_prefix("atx_h")
2611                                            .and_then(|s| s.strip_suffix("_marker"))
2612                                            .and_then(|s| s.parse::<u8>().ok())
2613                                            .unwrap_or(1);
2614                                    } else if h_child.kind() == "inline" {
2615                                        heading_name =
2616                                            node_text(source, &h_child).trim().to_string();
2617                                    }
2618                                    if !h_cursor.goto_next_sibling() {
2619                                        break;
2620                                    }
2621                                }
2622                            }
2623                        }
2624                        if !section_cursor.goto_next_sibling() {
2625                            break;
2626                        }
2627                    }
2628                }
2629
2630                if !heading_name.is_empty() {
2631                    let range = node_range(&child);
2632                    let signature = format!(
2633                        "{} {}",
2634                        "#".repeat((heading_level as usize).min(6)),
2635                        heading_name
2636                    );
2637
2638                    symbols.push(Symbol {
2639                        name: heading_name.clone(),
2640                        kind: SymbolKind::Heading,
2641                        range,
2642                        signature: Some(signature),
2643                        scope_chain: scope_chain.to_vec(),
2644                        exported: false,
2645                        parent: scope_chain.last().cloned(),
2646                    });
2647
2648                    // Recurse into the section for nested headings
2649                    let mut new_scope = scope_chain.to_vec();
2650                    new_scope.push(heading_name);
2651                    extract_md_sections(source, &child, symbols, &new_scope);
2652                }
2653            }
2654            _ => {}
2655        }
2656
2657        if !cursor.goto_next_sibling() {
2658            break;
2659        }
2660    }
2661}
2662
2663/// Remove duplicate symbols based on (name, kind, start_line).
2664/// Class declarations can match both "class" and "method" patterns,
2665/// producing duplicates.
2666fn dedup_symbols(symbols: &mut Vec<Symbol>) {
2667    let mut seen = std::collections::HashSet::new();
2668    symbols.retain(|s| {
2669        let key = (s.name.clone(), format!("{:?}", s.kind), s.range.start_line);
2670        seen.insert(key)
2671    });
2672}
2673
2674/// Provider that uses tree-sitter for real symbol extraction.
2675/// Implements the `LanguageProvider` trait from `language.rs`.
2676pub struct TreeSitterProvider {
2677    parser: RefCell<FileParser>,
2678}
2679
2680#[derive(Debug, Clone)]
2681struct ReExportTarget {
2682    file: PathBuf,
2683    symbol_name: String,
2684}
2685
2686impl TreeSitterProvider {
2687    /// Create a new `TreeSitterProvider` backed by a fresh `FileParser`.
2688    pub fn new() -> Self {
2689        Self {
2690            parser: RefCell::new(FileParser::new()),
2691        }
2692    }
2693
2694    /// Merge a pre-warmed symbol cache into the parser.
2695    /// Called from the main loop when the background indexer completes.
2696    pub fn merge_warm_cache(&self, cache: SymbolCache) {
2697        let mut parser = self.parser.borrow_mut();
2698        parser.set_warm_cache(cache);
2699    }
2700
2701    /// Return (local_cache_entries, warm_cache_entries) for status reporting.
2702    pub fn symbol_cache_stats(&self) -> (usize, usize) {
2703        let parser = self.parser.borrow();
2704        let local = parser.symbol_cache_len();
2705        let warm = parser.warm_cache_len();
2706        (local, warm)
2707    }
2708
2709    fn resolve_symbol_inner(
2710        &self,
2711        file: &Path,
2712        name: &str,
2713        depth: usize,
2714        visited: &mut HashSet<(PathBuf, String)>,
2715    ) -> Result<Vec<SymbolMatch>, AftError> {
2716        if depth > MAX_REEXPORT_DEPTH {
2717            return Ok(Vec::new());
2718        }
2719
2720        let canonical_file = std::fs::canonicalize(file).unwrap_or_else(|_| file.to_path_buf());
2721        if !visited.insert((canonical_file, name.to_string())) {
2722            return Ok(Vec::new());
2723        }
2724
2725        let symbols = self.parser.borrow_mut().extract_symbols(file)?;
2726        let local_matches = symbol_matches_in_file(file, &symbols, name);
2727        if !local_matches.is_empty() {
2728            return Ok(local_matches);
2729        }
2730
2731        if name == "default" {
2732            let default_matches = self.resolve_local_default_export(file, &symbols)?;
2733            if !default_matches.is_empty() {
2734                return Ok(default_matches);
2735            }
2736        }
2737
2738        let reexport_targets = self.collect_reexport_targets(file, name)?;
2739        let mut matches = Vec::new();
2740        let mut seen = HashSet::new();
2741        for target in reexport_targets {
2742            for resolved in
2743                self.resolve_symbol_inner(&target.file, &target.symbol_name, depth + 1, visited)?
2744            {
2745                let key = format!(
2746                    "{}:{}:{}:{}:{}:{}",
2747                    resolved.file,
2748                    resolved.symbol.name,
2749                    resolved.symbol.range.start_line,
2750                    resolved.symbol.range.start_col,
2751                    resolved.symbol.range.end_line,
2752                    resolved.symbol.range.end_col
2753                );
2754                if seen.insert(key) {
2755                    matches.push(resolved);
2756                }
2757            }
2758        }
2759
2760        Ok(matches)
2761    }
2762
2763    fn collect_reexport_targets(
2764        &self,
2765        file: &Path,
2766        requested_name: &str,
2767    ) -> Result<Vec<ReExportTarget>, AftError> {
2768        let (source, tree, lang) = self.read_parsed_file(file)?;
2769        if !matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
2770            return Ok(Vec::new());
2771        }
2772
2773        let mut targets = Vec::new();
2774        let root = tree.root_node();
2775        let from_dir = file.parent().unwrap_or_else(|| Path::new("."));
2776
2777        let mut cursor = root.walk();
2778        if !cursor.goto_first_child() {
2779            return Ok(targets);
2780        }
2781
2782        loop {
2783            let node = cursor.node();
2784            if node.kind() == "export_statement" {
2785                let Some(source_node) = node.child_by_field_name("source") else {
2786                    if !cursor.goto_next_sibling() {
2787                        break;
2788                    }
2789                    continue;
2790                };
2791
2792                let Some(module_path) = string_content(&source, &source_node) else {
2793                    if !cursor.goto_next_sibling() {
2794                        break;
2795                    }
2796                    continue;
2797                };
2798
2799                let Some(target_file) = resolve_module_path(from_dir, &module_path) else {
2800                    if !cursor.goto_next_sibling() {
2801                        break;
2802                    }
2803                    continue;
2804                };
2805
2806                if let Some(export_clause) = find_child_by_kind(node, "export_clause") {
2807                    if let Some(symbol_name) =
2808                        resolve_export_clause_name(&source, &export_clause, requested_name)
2809                    {
2810                        targets.push(ReExportTarget {
2811                            file: target_file,
2812                            symbol_name,
2813                        });
2814                    }
2815                } else if export_statement_has_wildcard(&source, &node) {
2816                    targets.push(ReExportTarget {
2817                        file: target_file,
2818                        symbol_name: requested_name.to_string(),
2819                    });
2820                }
2821            }
2822
2823            if !cursor.goto_next_sibling() {
2824                break;
2825            }
2826        }
2827
2828        Ok(targets)
2829    }
2830
2831    fn resolve_local_default_export(
2832        &self,
2833        file: &Path,
2834        symbols: &[Symbol],
2835    ) -> Result<Vec<SymbolMatch>, AftError> {
2836        let (source, tree, lang) = self.read_parsed_file(file)?;
2837        if !matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
2838            return Ok(Vec::new());
2839        }
2840
2841        let root = tree.root_node();
2842        let mut matches = Vec::new();
2843        let mut seen = HashSet::new();
2844
2845        let mut cursor = root.walk();
2846        if !cursor.goto_first_child() {
2847            return Ok(matches);
2848        }
2849
2850        loop {
2851            let node = cursor.node();
2852            if node.kind() == "export_statement"
2853                && node.child_by_field_name("source").is_none()
2854                && node_contains_token(&source, &node, "default")
2855            {
2856                if let Some(target_name) = default_export_target_name(&source, &node) {
2857                    for symbol_match in symbol_matches_in_file(file, symbols, &target_name) {
2858                        let key = format!(
2859                            "{}:{}:{}:{}:{}:{}",
2860                            symbol_match.file,
2861                            symbol_match.symbol.name,
2862                            symbol_match.symbol.range.start_line,
2863                            symbol_match.symbol.range.start_col,
2864                            symbol_match.symbol.range.end_line,
2865                            symbol_match.symbol.range.end_col
2866                        );
2867                        if seen.insert(key) {
2868                            matches.push(symbol_match);
2869                        }
2870                    }
2871                }
2872            }
2873
2874            if !cursor.goto_next_sibling() {
2875                break;
2876            }
2877        }
2878
2879        Ok(matches)
2880    }
2881
2882    fn read_parsed_file(&self, file: &Path) -> Result<(String, Tree, LangId), AftError> {
2883        let source = std::fs::read_to_string(file).map_err(|e| AftError::FileNotFound {
2884            path: format!("{}: {}", file.display(), e),
2885        })?;
2886        let (tree, lang) = {
2887            let mut parser = self.parser.borrow_mut();
2888            parser.parse_cloned(file)?
2889        };
2890        Ok((source, tree, lang))
2891    }
2892}
2893
2894fn symbol_matches_in_file(file: &Path, symbols: &[Symbol], name: &str) -> Vec<SymbolMatch> {
2895    symbols
2896        .iter()
2897        .filter(|symbol| symbol.name == name)
2898        .cloned()
2899        .map(|symbol| SymbolMatch {
2900            file: file.display().to_string(),
2901            symbol,
2902        })
2903        .collect()
2904}
2905
2906fn string_content(source: &str, node: &Node) -> Option<String> {
2907    let text = node_text(source, node);
2908    if text.len() < 2 {
2909        return None;
2910    }
2911
2912    Some(
2913        text.trim_start_matches(|c| c == '\'' || c == '"')
2914            .trim_end_matches(|c| c == '\'' || c == '"')
2915            .to_string(),
2916    )
2917}
2918
2919fn find_child_by_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
2920    let mut cursor = node.walk();
2921    if !cursor.goto_first_child() {
2922        return None;
2923    }
2924
2925    loop {
2926        let child = cursor.node();
2927        if child.kind() == kind {
2928            return Some(child);
2929        }
2930        if !cursor.goto_next_sibling() {
2931            break;
2932        }
2933    }
2934
2935    None
2936}
2937
2938fn resolve_export_clause_name(
2939    source: &str,
2940    export_clause: &Node,
2941    requested_name: &str,
2942) -> Option<String> {
2943    let mut cursor = export_clause.walk();
2944    if !cursor.goto_first_child() {
2945        return None;
2946    }
2947
2948    loop {
2949        let child = cursor.node();
2950        if child.kind() == "export_specifier" {
2951            let (source_name, exported_name) = export_specifier_names(source, &child)?;
2952            if exported_name == requested_name {
2953                return Some(source_name);
2954            }
2955        }
2956
2957        if !cursor.goto_next_sibling() {
2958            break;
2959        }
2960    }
2961
2962    None
2963}
2964
2965fn export_specifier_names(source: &str, specifier: &Node) -> Option<(String, String)> {
2966    let source_name = specifier
2967        .child_by_field_name("name")
2968        .map(|node| node_text(source, &node).to_string());
2969    let alias_name = specifier
2970        .child_by_field_name("alias")
2971        .map(|node| node_text(source, &node).to_string());
2972
2973    if let Some(source_name) = source_name {
2974        let exported_name = alias_name.unwrap_or_else(|| source_name.clone());
2975        return Some((source_name, exported_name));
2976    }
2977
2978    let mut names = Vec::new();
2979    let mut cursor = specifier.walk();
2980    if cursor.goto_first_child() {
2981        loop {
2982            let child = cursor.node();
2983            let child_text = node_text(source, &child).trim();
2984            if matches!(
2985                child.kind(),
2986                "identifier" | "type_identifier" | "property_identifier"
2987            ) || child_text == "default"
2988            {
2989                names.push(child_text.to_string());
2990            }
2991            if !cursor.goto_next_sibling() {
2992                break;
2993            }
2994        }
2995    }
2996
2997    match names.as_slice() {
2998        [name] => Some((name.clone(), name.clone())),
2999        [source_name, exported_name, ..] => Some((source_name.clone(), exported_name.clone())),
3000        _ => None,
3001    }
3002}
3003
3004fn export_statement_has_wildcard(source: &str, node: &Node) -> bool {
3005    let mut cursor = node.walk();
3006    if !cursor.goto_first_child() {
3007        return false;
3008    }
3009
3010    loop {
3011        if node_text(source, &cursor.node()).trim() == "*" {
3012            return true;
3013        }
3014        if !cursor.goto_next_sibling() {
3015            break;
3016        }
3017    }
3018
3019    false
3020}
3021
3022fn node_contains_token(source: &str, node: &Node, token: &str) -> bool {
3023    let mut cursor = node.walk();
3024    if !cursor.goto_first_child() {
3025        return false;
3026    }
3027
3028    loop {
3029        if node_text(source, &cursor.node()).trim() == token {
3030            return true;
3031        }
3032        if !cursor.goto_next_sibling() {
3033            break;
3034        }
3035    }
3036
3037    false
3038}
3039
3040fn default_export_target_name(source: &str, export_stmt: &Node) -> Option<String> {
3041    let mut cursor = export_stmt.walk();
3042    if !cursor.goto_first_child() {
3043        return None;
3044    }
3045
3046    loop {
3047        let child = cursor.node();
3048        match child.kind() {
3049            "function_declaration"
3050            | "class_declaration"
3051            | "interface_declaration"
3052            | "enum_declaration"
3053            | "type_alias_declaration"
3054            | "lexical_declaration" => {
3055                if let Some(name_node) = child.child_by_field_name("name") {
3056                    return Some(node_text(source, &name_node).to_string());
3057                }
3058
3059                if child.kind() == "lexical_declaration" {
3060                    let mut child_cursor = child.walk();
3061                    if child_cursor.goto_first_child() {
3062                        loop {
3063                            let nested = child_cursor.node();
3064                            if nested.kind() == "variable_declarator" {
3065                                if let Some(name_node) = nested.child_by_field_name("name") {
3066                                    return Some(node_text(source, &name_node).to_string());
3067                                }
3068                            }
3069                            if !child_cursor.goto_next_sibling() {
3070                                break;
3071                            }
3072                        }
3073                    }
3074                }
3075            }
3076            "identifier" | "type_identifier" => {
3077                let text = node_text(source, &child);
3078                if text != "export" && text != "default" {
3079                    return Some(text.to_string());
3080                }
3081            }
3082            _ => {}
3083        }
3084
3085        if !cursor.goto_next_sibling() {
3086            break;
3087        }
3088    }
3089
3090    None
3091}
3092
3093impl crate::language::LanguageProvider for TreeSitterProvider {
3094    fn resolve_symbol(&self, file: &Path, name: &str) -> Result<Vec<SymbolMatch>, AftError> {
3095        let matches = self.resolve_symbol_inner(file, name, 0, &mut HashSet::new())?;
3096
3097        if matches.is_empty() {
3098            Err(AftError::SymbolNotFound {
3099                name: name.to_string(),
3100                file: file.display().to_string(),
3101            })
3102        } else {
3103            Ok(matches)
3104        }
3105    }
3106
3107    fn list_symbols(&self, file: &Path) -> Result<Vec<Symbol>, AftError> {
3108        self.parser.borrow_mut().extract_symbols(file)
3109    }
3110
3111    fn as_any(&self) -> &dyn std::any::Any {
3112        self
3113    }
3114}
3115
3116#[cfg(test)]
3117mod tests {
3118    use super::*;
3119    use crate::language::LanguageProvider;
3120    use std::path::PathBuf;
3121
3122    fn fixture_path(name: &str) -> PathBuf {
3123        PathBuf::from(env!("CARGO_MANIFEST_DIR"))
3124            .join("tests")
3125            .join("fixtures")
3126            .join(name)
3127    }
3128
3129    // --- Language detection ---
3130
3131    #[test]
3132    fn detect_ts() {
3133        assert_eq!(
3134            detect_language(Path::new("foo.ts")),
3135            Some(LangId::TypeScript)
3136        );
3137    }
3138
3139    #[test]
3140    fn detect_tsx() {
3141        assert_eq!(detect_language(Path::new("foo.tsx")), Some(LangId::Tsx));
3142    }
3143
3144    #[test]
3145    fn detect_js() {
3146        assert_eq!(
3147            detect_language(Path::new("foo.js")),
3148            Some(LangId::JavaScript)
3149        );
3150    }
3151
3152    #[test]
3153    fn detect_jsx() {
3154        assert_eq!(
3155            detect_language(Path::new("foo.jsx")),
3156            Some(LangId::JavaScript)
3157        );
3158    }
3159
3160    #[test]
3161    fn detect_py() {
3162        assert_eq!(detect_language(Path::new("foo.py")), Some(LangId::Python));
3163    }
3164
3165    #[test]
3166    fn detect_rs() {
3167        assert_eq!(detect_language(Path::new("foo.rs")), Some(LangId::Rust));
3168    }
3169
3170    #[test]
3171    fn detect_go() {
3172        assert_eq!(detect_language(Path::new("foo.go")), Some(LangId::Go));
3173    }
3174
3175    #[test]
3176    fn detect_c() {
3177        assert_eq!(detect_language(Path::new("foo.c")), Some(LangId::C));
3178    }
3179
3180    #[test]
3181    fn detect_h() {
3182        assert_eq!(detect_language(Path::new("foo.h")), Some(LangId::C));
3183    }
3184
3185    #[test]
3186    fn detect_cc() {
3187        assert_eq!(detect_language(Path::new("foo.cc")), Some(LangId::Cpp));
3188    }
3189
3190    #[test]
3191    fn detect_cpp() {
3192        assert_eq!(detect_language(Path::new("foo.cpp")), Some(LangId::Cpp));
3193    }
3194
3195    #[test]
3196    fn detect_cxx() {
3197        assert_eq!(detect_language(Path::new("foo.cxx")), Some(LangId::Cpp));
3198    }
3199
3200    #[test]
3201    fn detect_hpp() {
3202        assert_eq!(detect_language(Path::new("foo.hpp")), Some(LangId::Cpp));
3203    }
3204
3205    #[test]
3206    fn detect_hh() {
3207        assert_eq!(detect_language(Path::new("foo.hh")), Some(LangId::Cpp));
3208    }
3209
3210    #[test]
3211    fn detect_zig() {
3212        assert_eq!(detect_language(Path::new("foo.zig")), Some(LangId::Zig));
3213    }
3214
3215    #[test]
3216    fn detect_cs() {
3217        assert_eq!(detect_language(Path::new("foo.cs")), Some(LangId::CSharp));
3218    }
3219
3220    #[test]
3221    fn detect_unknown_returns_none() {
3222        assert_eq!(detect_language(Path::new("foo.txt")), None);
3223    }
3224
3225    // --- Unsupported extension error ---
3226
3227    #[test]
3228    fn unsupported_extension_returns_invalid_request() {
3229        // Use a file that exists but has an unsupported extension
3230        let path = fixture_path("sample.ts");
3231        let bad_path = path.with_extension("txt");
3232        // Create a dummy file so the error comes from language detection, not I/O
3233        std::fs::write(&bad_path, "hello").unwrap();
3234        let provider = TreeSitterProvider::new();
3235        let result = provider.list_symbols(&bad_path);
3236        std::fs::remove_file(&bad_path).ok();
3237        match result {
3238            Err(AftError::InvalidRequest { message }) => {
3239                assert!(
3240                    message.contains("unsupported file extension"),
3241                    "msg: {}",
3242                    message
3243                );
3244                assert!(message.contains("txt"), "msg: {}", message);
3245            }
3246            other => panic!("expected InvalidRequest, got {:?}", other),
3247        }
3248    }
3249
3250    // --- TypeScript extraction ---
3251
3252    #[test]
3253    fn ts_extracts_all_symbol_kinds() {
3254        let provider = TreeSitterProvider::new();
3255        let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
3256
3257        let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
3258        assert!(
3259            names.contains(&"greet"),
3260            "missing function greet: {:?}",
3261            names
3262        );
3263        assert!(names.contains(&"add"), "missing arrow fn add: {:?}", names);
3264        assert!(
3265            names.contains(&"UserService"),
3266            "missing class UserService: {:?}",
3267            names
3268        );
3269        assert!(
3270            names.contains(&"Config"),
3271            "missing interface Config: {:?}",
3272            names
3273        );
3274        assert!(
3275            names.contains(&"Status"),
3276            "missing enum Status: {:?}",
3277            names
3278        );
3279        assert!(
3280            names.contains(&"UserId"),
3281            "missing type alias UserId: {:?}",
3282            names
3283        );
3284        assert!(
3285            names.contains(&"internalHelper"),
3286            "missing non-exported fn: {:?}",
3287            names
3288        );
3289
3290        // At least 6 unique symbols as required
3291        assert!(
3292            symbols.len() >= 6,
3293            "expected ≥6 symbols, got {}: {:?}",
3294            symbols.len(),
3295            names
3296        );
3297    }
3298
3299    #[test]
3300    fn ts_symbol_kinds_correct() {
3301        let provider = TreeSitterProvider::new();
3302        let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
3303
3304        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
3305
3306        assert_eq!(find("greet").kind, SymbolKind::Function);
3307        assert_eq!(find("add").kind, SymbolKind::Function); // arrow fn → Function
3308        assert_eq!(find("UserService").kind, SymbolKind::Class);
3309        assert_eq!(find("Config").kind, SymbolKind::Interface);
3310        assert_eq!(find("Status").kind, SymbolKind::Enum);
3311        assert_eq!(find("UserId").kind, SymbolKind::TypeAlias);
3312    }
3313
3314    #[test]
3315    fn ts_export_detection() {
3316        let provider = TreeSitterProvider::new();
3317        let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
3318
3319        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
3320
3321        assert!(find("greet").exported, "greet should be exported");
3322        assert!(find("add").exported, "add should be exported");
3323        assert!(
3324            find("UserService").exported,
3325            "UserService should be exported"
3326        );
3327        assert!(find("Config").exported, "Config should be exported");
3328        assert!(find("Status").exported, "Status should be exported");
3329        assert!(
3330            !find("internalHelper").exported,
3331            "internalHelper should not be exported"
3332        );
3333    }
3334
3335    #[test]
3336    fn ts_method_scope_chain() {
3337        let provider = TreeSitterProvider::new();
3338        let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
3339
3340        let methods: Vec<&Symbol> = symbols
3341            .iter()
3342            .filter(|s| s.kind == SymbolKind::Method)
3343            .collect();
3344        assert!(!methods.is_empty(), "should have at least one method");
3345
3346        for method in &methods {
3347            assert_eq!(
3348                method.scope_chain,
3349                vec!["UserService"],
3350                "method {} should have UserService in scope chain",
3351                method.name
3352            );
3353            assert_eq!(method.parent.as_deref(), Some("UserService"));
3354        }
3355    }
3356
3357    #[test]
3358    fn ts_signatures_present() {
3359        let provider = TreeSitterProvider::new();
3360        let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
3361
3362        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
3363
3364        let greet_sig = find("greet").signature.as_ref().unwrap();
3365        assert!(
3366            greet_sig.contains("greet"),
3367            "signature should contain function name: {}",
3368            greet_sig
3369        );
3370    }
3371
3372    #[test]
3373    fn ts_ranges_valid() {
3374        let provider = TreeSitterProvider::new();
3375        let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
3376
3377        for s in &symbols {
3378            assert!(
3379                s.range.end_line >= s.range.start_line,
3380                "symbol {} has invalid range: {:?}",
3381                s.name,
3382                s.range
3383            );
3384        }
3385    }
3386
3387    // --- JavaScript extraction ---
3388
3389    #[test]
3390    fn js_extracts_core_symbols() {
3391        let provider = TreeSitterProvider::new();
3392        let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
3393
3394        let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
3395        assert!(
3396            names.contains(&"multiply"),
3397            "missing function multiply: {:?}",
3398            names
3399        );
3400        assert!(
3401            names.contains(&"divide"),
3402            "missing arrow fn divide: {:?}",
3403            names
3404        );
3405        assert!(
3406            names.contains(&"EventEmitter"),
3407            "missing class EventEmitter: {:?}",
3408            names
3409        );
3410        assert!(
3411            names.contains(&"main"),
3412            "missing default export fn main: {:?}",
3413            names
3414        );
3415
3416        assert!(
3417            symbols.len() >= 4,
3418            "expected ≥4 symbols, got {}: {:?}",
3419            symbols.len(),
3420            names
3421        );
3422    }
3423
3424    #[test]
3425    fn js_arrow_fn_correctly_named() {
3426        let provider = TreeSitterProvider::new();
3427        let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
3428
3429        let divide = symbols.iter().find(|s| s.name == "divide").unwrap();
3430        assert_eq!(divide.kind, SymbolKind::Function);
3431        assert!(divide.exported, "divide should be exported");
3432
3433        let internal = symbols.iter().find(|s| s.name == "internalUtil").unwrap();
3434        assert_eq!(internal.kind, SymbolKind::Function);
3435        assert!(!internal.exported, "internalUtil should not be exported");
3436    }
3437
3438    #[test]
3439    fn js_method_scope_chain() {
3440        let provider = TreeSitterProvider::new();
3441        let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
3442
3443        let methods: Vec<&Symbol> = symbols
3444            .iter()
3445            .filter(|s| s.kind == SymbolKind::Method)
3446            .collect();
3447
3448        for method in &methods {
3449            assert_eq!(
3450                method.scope_chain,
3451                vec!["EventEmitter"],
3452                "method {} should have EventEmitter in scope chain",
3453                method.name
3454            );
3455        }
3456    }
3457
3458    // --- TSX extraction ---
3459
3460    #[test]
3461    fn tsx_extracts_react_component() {
3462        let provider = TreeSitterProvider::new();
3463        let symbols = provider.list_symbols(&fixture_path("sample.tsx")).unwrap();
3464
3465        let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
3466        assert!(
3467            names.contains(&"Button"),
3468            "missing React component Button: {:?}",
3469            names
3470        );
3471        assert!(
3472            names.contains(&"Counter"),
3473            "missing class Counter: {:?}",
3474            names
3475        );
3476        assert!(
3477            names.contains(&"formatLabel"),
3478            "missing function formatLabel: {:?}",
3479            names
3480        );
3481
3482        assert!(
3483            symbols.len() >= 2,
3484            "expected ≥2 symbols, got {}: {:?}",
3485            symbols.len(),
3486            names
3487        );
3488    }
3489
3490    #[test]
3491    fn tsx_jsx_doesnt_break_parser() {
3492        // Main assertion: TSX grammar handles JSX without errors
3493        let provider = TreeSitterProvider::new();
3494        let result = provider.list_symbols(&fixture_path("sample.tsx"));
3495        assert!(
3496            result.is_ok(),
3497            "TSX parsing should succeed: {:?}",
3498            result.err()
3499        );
3500    }
3501
3502    // --- resolve_symbol ---
3503
3504    #[test]
3505    fn resolve_symbol_finds_match() {
3506        let provider = TreeSitterProvider::new();
3507        let matches = provider
3508            .resolve_symbol(&fixture_path("sample.ts"), "greet")
3509            .unwrap();
3510        assert_eq!(matches.len(), 1);
3511        assert_eq!(matches[0].symbol.name, "greet");
3512        assert_eq!(matches[0].symbol.kind, SymbolKind::Function);
3513    }
3514
3515    #[test]
3516    fn resolve_symbol_not_found() {
3517        let provider = TreeSitterProvider::new();
3518        let result = provider.resolve_symbol(&fixture_path("sample.ts"), "nonexistent");
3519        assert!(matches!(result, Err(AftError::SymbolNotFound { .. })));
3520    }
3521
3522    #[test]
3523    fn resolve_symbol_follows_reexport_chains() {
3524        let dir = tempfile::tempdir().unwrap();
3525        let config = dir.path().join("config.ts");
3526        let barrel1 = dir.path().join("barrel1.ts");
3527        let barrel2 = dir.path().join("barrel2.ts");
3528        let barrel3 = dir.path().join("barrel3.ts");
3529        let index = dir.path().join("index.ts");
3530
3531        std::fs::write(
3532            &config,
3533            "export class Config {}\nexport default class DefaultConfig {}\n",
3534        )
3535        .unwrap();
3536        std::fs::write(
3537            &barrel1,
3538            "export { Config } from './config';\nexport { default as NamedDefault } from './config';\n",
3539        )
3540        .unwrap();
3541        std::fs::write(
3542            &barrel2,
3543            "export { Config as RenamedConfig } from './barrel1';\n",
3544        )
3545        .unwrap();
3546        std::fs::write(
3547            &barrel3,
3548            "export * from './barrel2';\nexport * from './barrel1';\n",
3549        )
3550        .unwrap();
3551        std::fs::write(
3552            &index,
3553            "export class Config {}\nexport { RenamedConfig as FinalConfig } from './barrel3';\nexport * from './barrel3';\n",
3554        )
3555        .unwrap();
3556
3557        let provider = TreeSitterProvider::new();
3558        let config_canon = std::fs::canonicalize(&config).unwrap();
3559
3560        let direct = provider.resolve_symbol(&barrel1, "Config").unwrap();
3561        assert_eq!(direct.len(), 1);
3562        assert_eq!(direct[0].symbol.name, "Config");
3563        assert_eq!(direct[0].file, config_canon.display().to_string());
3564
3565        let renamed = provider.resolve_symbol(&barrel2, "RenamedConfig").unwrap();
3566        assert_eq!(renamed.len(), 1);
3567        assert_eq!(renamed[0].symbol.name, "Config");
3568        assert_eq!(renamed[0].file, config_canon.display().to_string());
3569
3570        let wildcard_chain = provider.resolve_symbol(&index, "FinalConfig").unwrap();
3571        assert_eq!(wildcard_chain.len(), 1);
3572        assert_eq!(wildcard_chain[0].symbol.name, "Config");
3573        assert_eq!(wildcard_chain[0].file, config_canon.display().to_string());
3574
3575        let wildcard_default = provider.resolve_symbol(&index, "NamedDefault").unwrap();
3576        assert_eq!(wildcard_default.len(), 1);
3577        assert_eq!(wildcard_default[0].symbol.name, "DefaultConfig");
3578        assert_eq!(wildcard_default[0].file, config_canon.display().to_string());
3579
3580        let local = provider.resolve_symbol(&index, "Config").unwrap();
3581        assert_eq!(local.len(), 1);
3582        assert_eq!(local[0].symbol.name, "Config");
3583        assert_eq!(local[0].file, index.display().to_string());
3584    }
3585
3586    // --- Parse tree caching ---
3587
3588    #[test]
3589    fn symbol_range_includes_rust_attributes() {
3590        let dir = tempfile::tempdir().unwrap();
3591        let path = dir.path().join("test_attrs.rs");
3592        std::fs::write(
3593            &path,
3594            "/// This is a doc comment\n#[test]\n#[cfg(test)]\nfn my_test_fn() {\n    assert!(true);\n}\n",
3595        )
3596        .unwrap();
3597
3598        let provider = TreeSitterProvider::new();
3599        let matches = provider.resolve_symbol(&path, "my_test_fn").unwrap();
3600        assert_eq!(matches.len(), 1);
3601        assert_eq!(
3602            matches[0].symbol.range.start_line, 0,
3603            "symbol range should include preceding /// doc comment, got start_line={}",
3604            matches[0].symbol.range.start_line
3605        );
3606    }
3607
3608    #[test]
3609    fn symbol_range_includes_go_doc_comment() {
3610        let dir = tempfile::tempdir().unwrap();
3611        let path = dir.path().join("test_doc.go");
3612        std::fs::write(
3613            &path,
3614            "package main\n\n// MyFunc does something useful.\n// It has a multi-line doc.\nfunc MyFunc() {\n}\n",
3615        )
3616        .unwrap();
3617
3618        let provider = TreeSitterProvider::new();
3619        let matches = provider.resolve_symbol(&path, "MyFunc").unwrap();
3620        assert_eq!(matches.len(), 1);
3621        assert_eq!(
3622            matches[0].symbol.range.start_line, 2,
3623            "symbol range should include preceding doc comments, got start_line={}",
3624            matches[0].symbol.range.start_line
3625        );
3626    }
3627
3628    #[test]
3629    fn symbol_range_skips_unrelated_comments() {
3630        let dir = tempfile::tempdir().unwrap();
3631        let path = dir.path().join("test_gap.go");
3632        std::fs::write(
3633            &path,
3634            "package main\n\n// This is a standalone comment\n\nfunc Standalone() {\n}\n",
3635        )
3636        .unwrap();
3637
3638        let provider = TreeSitterProvider::new();
3639        let matches = provider.resolve_symbol(&path, "Standalone").unwrap();
3640        assert_eq!(matches.len(), 1);
3641        assert_eq!(
3642            matches[0].symbol.range.start_line, 4,
3643            "symbol range should NOT include comment separated by blank line, got start_line={}",
3644            matches[0].symbol.range.start_line
3645        );
3646    }
3647
3648    #[test]
3649    fn parse_cache_returns_same_tree() {
3650        let mut parser = FileParser::new();
3651        let path = fixture_path("sample.ts");
3652
3653        let (tree1, _) = parser.parse(&path).unwrap();
3654        let tree1_root = tree1.root_node().byte_range();
3655
3656        let (tree2, _) = parser.parse(&path).unwrap();
3657        let tree2_root = tree2.root_node().byte_range();
3658
3659        // Same tree (cache hit) should return identical root node range
3660        assert_eq!(tree1_root, tree2_root);
3661    }
3662
3663    // --- Python extraction ---
3664
3665    #[test]
3666    fn py_extracts_all_symbols() {
3667        let provider = TreeSitterProvider::new();
3668        let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
3669
3670        let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
3671        assert!(
3672            names.contains(&"top_level_function"),
3673            "missing top_level_function: {:?}",
3674            names
3675        );
3676        assert!(names.contains(&"MyClass"), "missing MyClass: {:?}", names);
3677        assert!(
3678            names.contains(&"instance_method"),
3679            "missing method instance_method: {:?}",
3680            names
3681        );
3682        assert!(
3683            names.contains(&"decorated_function"),
3684            "missing decorated_function: {:?}",
3685            names
3686        );
3687
3688        // Plan requires ≥4 symbols
3689        assert!(
3690            symbols.len() >= 4,
3691            "expected ≥4 symbols, got {}: {:?}",
3692            symbols.len(),
3693            names
3694        );
3695    }
3696
3697    #[test]
3698    fn py_symbol_kinds_correct() {
3699        let provider = TreeSitterProvider::new();
3700        let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
3701
3702        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
3703
3704        assert_eq!(find("top_level_function").kind, SymbolKind::Function);
3705        assert_eq!(find("MyClass").kind, SymbolKind::Class);
3706        assert_eq!(find("instance_method").kind, SymbolKind::Method);
3707        assert_eq!(find("decorated_function").kind, SymbolKind::Function);
3708        assert_eq!(find("OuterClass").kind, SymbolKind::Class);
3709        assert_eq!(find("InnerClass").kind, SymbolKind::Class);
3710        assert_eq!(find("inner_method").kind, SymbolKind::Method);
3711        assert_eq!(find("outer_method").kind, SymbolKind::Method);
3712    }
3713
3714    #[test]
3715    fn py_method_scope_chain() {
3716        let provider = TreeSitterProvider::new();
3717        let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
3718
3719        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
3720
3721        // Method inside MyClass
3722        assert_eq!(
3723            find("instance_method").scope_chain,
3724            vec!["MyClass"],
3725            "instance_method should have MyClass in scope chain"
3726        );
3727        assert_eq!(find("instance_method").parent.as_deref(), Some("MyClass"));
3728
3729        // Method inside OuterClass > InnerClass
3730        assert_eq!(
3731            find("inner_method").scope_chain,
3732            vec!["OuterClass", "InnerClass"],
3733            "inner_method should have nested scope chain"
3734        );
3735
3736        // InnerClass itself should have OuterClass in scope
3737        assert_eq!(
3738            find("InnerClass").scope_chain,
3739            vec!["OuterClass"],
3740            "InnerClass should have OuterClass in scope"
3741        );
3742
3743        // Top-level function has empty scope
3744        assert!(
3745            find("top_level_function").scope_chain.is_empty(),
3746            "top-level function should have empty scope chain"
3747        );
3748    }
3749
3750    #[test]
3751    fn py_decorated_function_signature() {
3752        let provider = TreeSitterProvider::new();
3753        let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
3754
3755        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
3756
3757        let sig = find("decorated_function").signature.as_ref().unwrap();
3758        assert!(
3759            sig.contains("@staticmethod"),
3760            "decorated function signature should include decorator: {}",
3761            sig
3762        );
3763        assert!(
3764            sig.contains("def decorated_function"),
3765            "signature should include function def: {}",
3766            sig
3767        );
3768    }
3769
3770    #[test]
3771    fn py_ranges_valid() {
3772        let provider = TreeSitterProvider::new();
3773        let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
3774
3775        for s in &symbols {
3776            assert!(
3777                s.range.end_line >= s.range.start_line,
3778                "symbol {} has invalid range: {:?}",
3779                s.name,
3780                s.range
3781            );
3782        }
3783    }
3784
3785    // --- Rust extraction ---
3786
3787    #[test]
3788    fn rs_extracts_all_symbols() {
3789        let provider = TreeSitterProvider::new();
3790        let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
3791
3792        let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
3793        assert!(
3794            names.contains(&"public_function"),
3795            "missing public_function: {:?}",
3796            names
3797        );
3798        assert!(
3799            names.contains(&"private_function"),
3800            "missing private_function: {:?}",
3801            names
3802        );
3803        assert!(names.contains(&"MyStruct"), "missing MyStruct: {:?}", names);
3804        assert!(names.contains(&"Color"), "missing enum Color: {:?}", names);
3805        assert!(
3806            names.contains(&"Drawable"),
3807            "missing trait Drawable: {:?}",
3808            names
3809        );
3810        // impl methods
3811        assert!(
3812            names.contains(&"new"),
3813            "missing impl method new: {:?}",
3814            names
3815        );
3816
3817        // Plan requires ≥6 symbols
3818        assert!(
3819            symbols.len() >= 6,
3820            "expected ≥6 symbols, got {}: {:?}",
3821            symbols.len(),
3822            names
3823        );
3824    }
3825
3826    #[test]
3827    fn rs_symbol_kinds_correct() {
3828        let provider = TreeSitterProvider::new();
3829        let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
3830
3831        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
3832
3833        assert_eq!(find("public_function").kind, SymbolKind::Function);
3834        assert_eq!(find("private_function").kind, SymbolKind::Function);
3835        assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
3836        assert_eq!(find("Color").kind, SymbolKind::Enum);
3837        assert_eq!(find("Drawable").kind, SymbolKind::Interface); // trait → Interface
3838        assert_eq!(find("new").kind, SymbolKind::Method);
3839    }
3840
3841    #[test]
3842    fn rs_pub_export_detection() {
3843        let provider = TreeSitterProvider::new();
3844        let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
3845
3846        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
3847
3848        assert!(
3849            find("public_function").exported,
3850            "pub fn should be exported"
3851        );
3852        assert!(
3853            !find("private_function").exported,
3854            "non-pub fn should not be exported"
3855        );
3856        assert!(find("MyStruct").exported, "pub struct should be exported");
3857        assert!(find("Color").exported, "pub enum should be exported");
3858        assert!(find("Drawable").exported, "pub trait should be exported");
3859        assert!(
3860            find("new").exported,
3861            "pub fn inside impl should be exported"
3862        );
3863        assert!(
3864            !find("helper").exported,
3865            "non-pub fn inside impl should not be exported"
3866        );
3867    }
3868
3869    #[test]
3870    fn rs_impl_method_scope_chain() {
3871        let provider = TreeSitterProvider::new();
3872        let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
3873
3874        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
3875
3876        // `impl MyStruct { fn new() }` → scope chain = ["MyStruct"]
3877        assert_eq!(
3878            find("new").scope_chain,
3879            vec!["MyStruct"],
3880            "impl method should have type in scope chain"
3881        );
3882        assert_eq!(find("new").parent.as_deref(), Some("MyStruct"));
3883
3884        // Free function has empty scope chain
3885        assert!(
3886            find("public_function").scope_chain.is_empty(),
3887            "free function should have empty scope chain"
3888        );
3889    }
3890
3891    #[test]
3892    fn rs_trait_impl_scope_chain() {
3893        let provider = TreeSitterProvider::new();
3894        let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
3895
3896        // `impl Drawable for MyStruct { fn draw() }` → scope = ["Drawable for MyStruct"]
3897        let draw = symbols.iter().find(|s| s.name == "draw").unwrap();
3898        assert_eq!(
3899            draw.scope_chain,
3900            vec!["Drawable for MyStruct"],
3901            "trait impl method should have 'Trait for Type' scope"
3902        );
3903        assert_eq!(draw.parent.as_deref(), Some("MyStruct"));
3904    }
3905
3906    #[test]
3907    fn rs_ranges_valid() {
3908        let provider = TreeSitterProvider::new();
3909        let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
3910
3911        for s in &symbols {
3912            assert!(
3913                s.range.end_line >= s.range.start_line,
3914                "symbol {} has invalid range: {:?}",
3915                s.name,
3916                s.range
3917            );
3918        }
3919    }
3920
3921    // --- Go extraction ---
3922
3923    #[test]
3924    fn go_extracts_all_symbols() {
3925        let provider = TreeSitterProvider::new();
3926        let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
3927
3928        let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
3929        assert!(
3930            names.contains(&"ExportedFunction"),
3931            "missing ExportedFunction: {:?}",
3932            names
3933        );
3934        assert!(
3935            names.contains(&"unexportedFunction"),
3936            "missing unexportedFunction: {:?}",
3937            names
3938        );
3939        assert!(
3940            names.contains(&"MyStruct"),
3941            "missing struct MyStruct: {:?}",
3942            names
3943        );
3944        assert!(
3945            names.contains(&"Reader"),
3946            "missing interface Reader: {:?}",
3947            names
3948        );
3949        // receiver method
3950        assert!(
3951            names.contains(&"String"),
3952            "missing receiver method String: {:?}",
3953            names
3954        );
3955
3956        // Plan requires ≥4 symbols
3957        assert!(
3958            symbols.len() >= 4,
3959            "expected ≥4 symbols, got {}: {:?}",
3960            symbols.len(),
3961            names
3962        );
3963    }
3964
3965    #[test]
3966    fn go_symbol_kinds_correct() {
3967        let provider = TreeSitterProvider::new();
3968        let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
3969
3970        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
3971
3972        assert_eq!(find("ExportedFunction").kind, SymbolKind::Function);
3973        assert_eq!(find("unexportedFunction").kind, SymbolKind::Function);
3974        assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
3975        assert_eq!(find("Reader").kind, SymbolKind::Interface);
3976        assert_eq!(find("String").kind, SymbolKind::Method);
3977        assert_eq!(find("helper").kind, SymbolKind::Method);
3978    }
3979
3980    #[test]
3981    fn go_uppercase_export_detection() {
3982        let provider = TreeSitterProvider::new();
3983        let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
3984
3985        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
3986
3987        assert!(
3988            find("ExportedFunction").exported,
3989            "ExportedFunction (uppercase) should be exported"
3990        );
3991        assert!(
3992            !find("unexportedFunction").exported,
3993            "unexportedFunction (lowercase) should not be exported"
3994        );
3995        assert!(
3996            find("MyStruct").exported,
3997            "MyStruct (uppercase) should be exported"
3998        );
3999        assert!(
4000            find("Reader").exported,
4001            "Reader (uppercase) should be exported"
4002        );
4003        assert!(
4004            find("String").exported,
4005            "String method (uppercase) should be exported"
4006        );
4007        assert!(
4008            !find("helper").exported,
4009            "helper method (lowercase) should not be exported"
4010        );
4011    }
4012
4013    #[test]
4014    fn go_receiver_method_scope_chain() {
4015        let provider = TreeSitterProvider::new();
4016        let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
4017
4018        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
4019
4020        // `func (m *MyStruct) String()` → scope chain = ["MyStruct"]
4021        assert_eq!(
4022            find("String").scope_chain,
4023            vec!["MyStruct"],
4024            "receiver method should have type in scope chain"
4025        );
4026        assert_eq!(find("String").parent.as_deref(), Some("MyStruct"));
4027
4028        // Regular function has empty scope chain
4029        assert!(
4030            find("ExportedFunction").scope_chain.is_empty(),
4031            "regular function should have empty scope chain"
4032        );
4033    }
4034
4035    #[test]
4036    fn go_ranges_valid() {
4037        let provider = TreeSitterProvider::new();
4038        let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
4039
4040        for s in &symbols {
4041            assert!(
4042                s.range.end_line >= s.range.start_line,
4043                "symbol {} has invalid range: {:?}",
4044                s.name,
4045                s.range
4046            );
4047        }
4048    }
4049
4050    // --- Cross-language ---
4051
4052    #[test]
4053    fn cross_language_all_six_produce_symbols() {
4054        let provider = TreeSitterProvider::new();
4055
4056        let fixtures = [
4057            ("sample.ts", "TypeScript"),
4058            ("sample.tsx", "TSX"),
4059            ("sample.js", "JavaScript"),
4060            ("sample.py", "Python"),
4061            ("sample.rs", "Rust"),
4062            ("sample.go", "Go"),
4063        ];
4064
4065        for (fixture, lang) in &fixtures {
4066            let symbols = provider
4067                .list_symbols(&fixture_path(fixture))
4068                .unwrap_or_else(|e| panic!("{} ({}) failed: {:?}", lang, fixture, e));
4069            assert!(
4070                symbols.len() >= 2,
4071                "{} should produce ≥2 symbols, got {}: {:?}",
4072                lang,
4073                symbols.len(),
4074                symbols.iter().map(|s| &s.name).collect::<Vec<_>>()
4075            );
4076        }
4077    }
4078
4079    // --- Symbol cache tests ---
4080
4081    #[test]
4082    fn symbol_cache_returns_cached_results_on_second_call() {
4083        let dir = tempfile::tempdir().unwrap();
4084        let file = dir.path().join("test.rs");
4085        std::fs::write(&file, "pub fn hello() {}\npub fn world() {}").unwrap();
4086
4087        let mut parser = FileParser::new();
4088
4089        let symbols1 = parser.extract_symbols(&file).unwrap();
4090        assert_eq!(symbols1.len(), 2);
4091
4092        // Second call should return cached result
4093        let symbols2 = parser.extract_symbols(&file).unwrap();
4094        assert_eq!(symbols2.len(), 2);
4095        assert_eq!(symbols1[0].name, symbols2[0].name);
4096
4097        // Verify cache is populated
4098        assert!(parser.symbol_cache.contains_key(&file));
4099    }
4100
4101    #[test]
4102    fn symbol_cache_invalidates_on_file_change() {
4103        let dir = tempfile::tempdir().unwrap();
4104        let file = dir.path().join("test.rs");
4105        std::fs::write(&file, "pub fn hello() {}").unwrap();
4106
4107        let mut parser = FileParser::new();
4108
4109        let symbols1 = parser.extract_symbols(&file).unwrap();
4110        assert_eq!(symbols1.len(), 1);
4111        assert_eq!(symbols1[0].name, "hello");
4112
4113        // Wait to ensure mtime changes (filesystem resolution can be 1s on some OS)
4114        std::thread::sleep(std::time::Duration::from_millis(50));
4115
4116        // Modify file — add a second function
4117        std::fs::write(&file, "pub fn hello() {}\npub fn goodbye() {}").unwrap();
4118
4119        // Should detect mtime change and re-extract
4120        let symbols2 = parser.extract_symbols(&file).unwrap();
4121        assert_eq!(symbols2.len(), 2);
4122        assert!(symbols2.iter().any(|s| s.name == "goodbye"));
4123    }
4124
4125    #[test]
4126    fn symbol_cache_invalidate_method_clears_entry() {
4127        let dir = tempfile::tempdir().unwrap();
4128        let file = dir.path().join("test.rs");
4129        std::fs::write(&file, "pub fn hello() {}").unwrap();
4130
4131        let mut parser = FileParser::new();
4132        parser.extract_symbols(&file).unwrap();
4133        assert!(parser.symbol_cache.contains_key(&file));
4134
4135        parser.invalidate_symbols(&file);
4136        assert!(!parser.symbol_cache.contains_key(&file));
4137        // Parse tree cache should also be cleared
4138        assert!(!parser.cache.contains_key(&file));
4139    }
4140
4141    #[test]
4142    fn symbol_cache_works_for_multiple_languages() {
4143        let dir = tempfile::tempdir().unwrap();
4144        let rs_file = dir.path().join("lib.rs");
4145        let ts_file = dir.path().join("app.ts");
4146        let py_file = dir.path().join("main.py");
4147
4148        std::fs::write(&rs_file, "pub fn rust_fn() {}").unwrap();
4149        std::fs::write(&ts_file, "export function tsFn() {}").unwrap();
4150        std::fs::write(&py_file, "def py_fn():\n    pass").unwrap();
4151
4152        let mut parser = FileParser::new();
4153
4154        let rs_syms = parser.extract_symbols(&rs_file).unwrap();
4155        let ts_syms = parser.extract_symbols(&ts_file).unwrap();
4156        let py_syms = parser.extract_symbols(&py_file).unwrap();
4157
4158        assert!(rs_syms.iter().any(|s| s.name == "rust_fn"));
4159        assert!(ts_syms.iter().any(|s| s.name == "tsFn"));
4160        assert!(py_syms.iter().any(|s| s.name == "py_fn"));
4161
4162        // All should be cached now
4163        assert_eq!(parser.symbol_cache.len(), 3);
4164
4165        // Re-extract should return same results from cache
4166        let rs_syms2 = parser.extract_symbols(&rs_file).unwrap();
4167        assert_eq!(rs_syms.len(), rs_syms2.len());
4168    }
4169}