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