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