Skip to main content

aft/
parser.rs

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