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