Skip to main content

aft/
parser.rs

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