Skip to main content

aft/
parser.rs

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