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
386const JAVA_QUERY: &str = r#"
387;; types
388(class_declaration
389  name: (identifier) @class.name) @class.def
390(interface_declaration
391  name: (identifier) @interface.name) @interface.def
392(annotation_type_declaration
393  name: (identifier) @interface.name) @interface.def
394(enum_declaration
395  name: (identifier) @enum.name) @enum.def
396(record_declaration
397  name: (identifier) @struct.name) @struct.def
398
399;; members
400(method_declaration
401  name: (identifier) @fn.name) @fn.def
402(constructor_declaration
403  name: (identifier) @fn.name) @fn.def
404(field_declaration
405  declarator: (variable_declarator
406    name: (identifier) @var.name)) @var.def
407"#;
408
409const RUBY_QUERY: &str = r#"
410;; modules / classes
411(module
412  name: (constant) @module.name) @module.def
413(class
414  name: (constant) @class.name) @class.def
415
416;; methods
417(method
418  name: (_) @fn.name) @fn.def
419(singleton_method
420  name: (_) @fn.name) @fn.def
421
422;; constants
423(assignment
424  left: (constant) @var.name) @var.def
425"#;
426
427const KOTLIN_QUERY: &str = r#"
428;; declarations
429(class_declaration
430  (type_identifier) @class.name) @class.def
431(object_declaration
432  (type_identifier) @object.name) @object.def
433(function_declaration
434  (simple_identifier) @fn.name) @fn.def
435(property_declaration
436  (variable_declaration
437    (simple_identifier) @var.name)) @var.def
438(type_alias
439  (type_identifier) @type.name) @type.def
440"#;
441
442const SWIFT_QUERY: &str = r#"
443;; types
444(class_declaration
445  name: (type_identifier) @class.name) @class.def
446(protocol_declaration
447  name: (type_identifier) @interface.name) @interface.def
448
449;; functions and members
450(function_declaration
451  name: (simple_identifier) @fn.name) @fn.def
452(protocol_function_declaration
453  name: (simple_identifier) @fn.name) @fn.def
454(property_declaration
455  name: (pattern
456    bound_identifier: (simple_identifier) @var.name)) @var.def
457(typealias_declaration
458  name: (type_identifier) @type.name) @type.def
459"#;
460
461const PHP_QUERY: &str = r#"
462;; namespaces and types
463(namespace_definition
464  name: (namespace_name) @namespace.name) @namespace.def
465(class_declaration
466  name: (name) @class.name) @class.def
467(interface_declaration
468  name: (name) @interface.name) @interface.def
469(trait_declaration
470  name: (name) @trait.name) @trait.def
471(enum_declaration
472  name: (name) @enum.name) @enum.def
473
474;; functions and members
475(function_definition
476  name: (name) @fn.name) @fn.def
477(method_declaration
478  name: (name) @fn.name) @fn.def
479(property_declaration
480  (property_element
481    name: (variable_name (name) @var.name))) @var.def
482"#;
483
484const LUA_QUERY: &str = r#"
485;; functions
486(function_declaration
487  name: (identifier) @fn.name) @fn.def
488(function_declaration
489  name: (dot_index_expression
490    field: (identifier) @fn.name)) @fn.def
491(function_declaration
492  name: (method_index_expression
493    method: (identifier) @fn.name)) @fn.def
494
495;; locals / module tables
496(variable_declaration
497  (assignment_statement
498    (variable_list
499      name: (identifier) @var.name))) @var.def
500"#;
501
502const PERL_QUERY: &str = r#"
503;; packages and subroutines
504(package_statement
505  (package_name) @package.name) @package.def
506(function_definition
507  name: (identifier) @fn.name) @fn.def
508(function_definition_without_sub
509  name: (identifier) @fn.name) @fn.def
510
511;; constants / lexical variables
512(use_constant_statement
513  constant: (identifier) @var.name) @var.def
514(variable_declaration
515  variable_name: (_) @var.name) @var.def
516"#;
517
518/// Supported language identifier.
519#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
520pub enum LangId {
521    TypeScript,
522    Tsx,
523    JavaScript,
524    Python,
525    Rust,
526    Go,
527    C,
528    Cpp,
529    Zig,
530    CSharp,
531    Bash,
532    Html,
533    Markdown,
534    Solidity,
535    Vue,
536    Json,
537    Scala,
538    Java,
539    Ruby,
540    Kotlin,
541    Swift,
542    Php,
543    Lua,
544    Perl,
545}
546
547/// Maps file extension to language identifier.
548pub fn detect_language(path: &Path) -> Option<LangId> {
549    let ext = path.extension()?.to_str()?;
550    match ext {
551        "ts" => Some(LangId::TypeScript),
552        "tsx" => Some(LangId::Tsx),
553        "js" | "jsx" => Some(LangId::JavaScript),
554        "py" => Some(LangId::Python),
555        "rs" => Some(LangId::Rust),
556        "go" => Some(LangId::Go),
557        "c" | "h" => Some(LangId::C),
558        "cc" | "cpp" | "cxx" | "hpp" | "hh" => Some(LangId::Cpp),
559        "zig" => Some(LangId::Zig),
560        "cs" => Some(LangId::CSharp),
561        "sh" | "bash" | "zsh" => Some(LangId::Bash),
562        "html" | "htm" => Some(LangId::Html),
563        "md" | "markdown" | "mdx" => Some(LangId::Markdown),
564        "sol" => Some(LangId::Solidity),
565        "vue" => Some(LangId::Vue),
566        "json" | "jsonc" => Some(LangId::Json),
567        "scala" | "sc" => Some(LangId::Scala),
568        "java" => Some(LangId::Java),
569        "rb" => Some(LangId::Ruby),
570        "kt" | "kts" => Some(LangId::Kotlin),
571        "swift" => Some(LangId::Swift),
572        "php" => Some(LangId::Php),
573        "lua" => Some(LangId::Lua),
574        "pl" | "pm" | "t" => Some(LangId::Perl),
575        _ => None,
576    }
577}
578
579/// Returns the tree-sitter Language grammar for a given LangId.
580pub fn grammar_for(lang: LangId) -> Language {
581    match lang {
582        LangId::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
583        LangId::Tsx => tree_sitter_typescript::LANGUAGE_TSX.into(),
584        LangId::JavaScript => tree_sitter_javascript::LANGUAGE.into(),
585        LangId::Python => tree_sitter_python::LANGUAGE.into(),
586        LangId::Rust => tree_sitter_rust::LANGUAGE.into(),
587        LangId::Go => tree_sitter_go::LANGUAGE.into(),
588        LangId::C => tree_sitter_c::LANGUAGE.into(),
589        LangId::Cpp => tree_sitter_cpp::LANGUAGE.into(),
590        LangId::Zig => tree_sitter_zig::LANGUAGE.into(),
591        LangId::CSharp => tree_sitter_c_sharp::LANGUAGE.into(),
592        LangId::Bash => tree_sitter_bash::LANGUAGE.into(),
593        LangId::Html => tree_sitter_html::LANGUAGE.into(),
594        LangId::Markdown => tree_sitter_md::LANGUAGE.into(),
595        LangId::Solidity => tree_sitter_solidity::LANGUAGE.into(),
596        LangId::Vue => tree_sitter_vue::LANGUAGE.into(),
597        LangId::Json => tree_sitter_json::LANGUAGE.into(),
598        LangId::Scala => tree_sitter_scala::LANGUAGE.into(),
599        LangId::Java => tree_sitter_java::LANGUAGE.into(),
600        LangId::Ruby => tree_sitter_ruby::LANGUAGE.into(),
601        LangId::Kotlin => tree_sitter_kotlin_sg::LANGUAGE.into(),
602        LangId::Swift => tree_sitter_swift::LANGUAGE.into(),
603        LangId::Php => tree_sitter_php::LANGUAGE_PHP.into(),
604        LangId::Lua => tree_sitter_lua::LANGUAGE.into(),
605        LangId::Perl => tree_sitter_perl::LANGUAGE.into(),
606    }
607}
608
609/// Returns the query pattern string for a given LangId, if implemented.
610fn query_for(lang: LangId) -> Option<&'static str> {
611    match lang {
612        LangId::TypeScript | LangId::Tsx => Some(TS_QUERY),
613        LangId::JavaScript => Some(JS_QUERY),
614        LangId::Python => Some(PY_QUERY),
615        LangId::Rust => Some(RS_QUERY),
616        LangId::Go => Some(GO_QUERY),
617        LangId::C => Some(C_QUERY),
618        LangId::Cpp => Some(CPP_QUERY),
619        LangId::Zig => Some(ZIG_QUERY),
620        LangId::CSharp => Some(CSHARP_QUERY),
621        LangId::Bash => Some(BASH_QUERY),
622        LangId::Html => None, // HTML uses direct tree walking like Markdown
623        LangId::Markdown => None,
624        LangId::Solidity => Some(SOL_QUERY),
625        LangId::Vue => None,
626        LangId::Json => None,
627        LangId::Scala => Some(SCALA_QUERY),
628        LangId::Java => Some(JAVA_QUERY),
629        LangId::Ruby => Some(RUBY_QUERY),
630        LangId::Kotlin => Some(KOTLIN_QUERY),
631        LangId::Swift => Some(SWIFT_QUERY),
632        LangId::Php => Some(PHP_QUERY),
633        LangId::Lua => Some(LUA_QUERY),
634        LangId::Perl => Some(PERL_QUERY),
635    }
636}
637
638static TS_QUERY_CACHE: LazyLock<Result<Query, String>> =
639    LazyLock::new(|| compile_query(LangId::TypeScript));
640static TSX_QUERY_CACHE: LazyLock<Result<Query, String>> =
641    LazyLock::new(|| compile_query(LangId::Tsx));
642static JS_QUERY_CACHE: LazyLock<Result<Query, String>> =
643    LazyLock::new(|| compile_query(LangId::JavaScript));
644static PY_QUERY_CACHE: LazyLock<Result<Query, String>> =
645    LazyLock::new(|| compile_query(LangId::Python));
646static RS_QUERY_CACHE: LazyLock<Result<Query, String>> =
647    LazyLock::new(|| compile_query(LangId::Rust));
648static GO_QUERY_CACHE: LazyLock<Result<Query, String>> =
649    LazyLock::new(|| compile_query(LangId::Go));
650static C_QUERY_CACHE: LazyLock<Result<Query, String>> = LazyLock::new(|| compile_query(LangId::C));
651static CPP_QUERY_CACHE: LazyLock<Result<Query, String>> =
652    LazyLock::new(|| compile_query(LangId::Cpp));
653static ZIG_QUERY_CACHE: LazyLock<Result<Query, String>> =
654    LazyLock::new(|| compile_query(LangId::Zig));
655static CSHARP_QUERY_CACHE: LazyLock<Result<Query, String>> =
656    LazyLock::new(|| compile_query(LangId::CSharp));
657static BASH_QUERY_CACHE: LazyLock<Result<Query, String>> =
658    LazyLock::new(|| compile_query(LangId::Bash));
659static SOL_QUERY_CACHE: LazyLock<Result<Query, String>> =
660    LazyLock::new(|| compile_query(LangId::Solidity));
661static SCALA_QUERY_CACHE: LazyLock<Result<Query, String>> =
662    LazyLock::new(|| compile_query(LangId::Scala));
663static JAVA_QUERY_CACHE: LazyLock<Result<Query, String>> =
664    LazyLock::new(|| compile_query(LangId::Java));
665static RUBY_QUERY_CACHE: LazyLock<Result<Query, String>> =
666    LazyLock::new(|| compile_query(LangId::Ruby));
667static KOTLIN_QUERY_CACHE: LazyLock<Result<Query, String>> =
668    LazyLock::new(|| compile_query(LangId::Kotlin));
669static SWIFT_QUERY_CACHE: LazyLock<Result<Query, String>> =
670    LazyLock::new(|| compile_query(LangId::Swift));
671static PHP_QUERY_CACHE: LazyLock<Result<Query, String>> =
672    LazyLock::new(|| compile_query(LangId::Php));
673static LUA_QUERY_CACHE: LazyLock<Result<Query, String>> =
674    LazyLock::new(|| compile_query(LangId::Lua));
675static PERL_QUERY_CACHE: LazyLock<Result<Query, String>> =
676    LazyLock::new(|| compile_query(LangId::Perl));
677
678fn compile_query(lang: LangId) -> Result<Query, String> {
679    let query_src = query_for(lang).ok_or_else(|| format!("missing query for {lang:?}"))?;
680    let grammar = grammar_for(lang);
681    Query::new(&grammar, query_src)
682        .map_err(|error| format!("query compile error for {lang:?}: {error}"))
683}
684
685fn cached_query_for(lang: LangId) -> Result<Option<&'static Query>, AftError> {
686    let query = match lang {
687        LangId::TypeScript => Some(&*TS_QUERY_CACHE),
688        LangId::Tsx => Some(&*TSX_QUERY_CACHE),
689        LangId::JavaScript => Some(&*JS_QUERY_CACHE),
690        LangId::Python => Some(&*PY_QUERY_CACHE),
691        LangId::Rust => Some(&*RS_QUERY_CACHE),
692        LangId::Go => Some(&*GO_QUERY_CACHE),
693        LangId::C => Some(&*C_QUERY_CACHE),
694        LangId::Cpp => Some(&*CPP_QUERY_CACHE),
695        LangId::Zig => Some(&*ZIG_QUERY_CACHE),
696        LangId::CSharp => Some(&*CSHARP_QUERY_CACHE),
697        LangId::Bash => Some(&*BASH_QUERY_CACHE),
698        LangId::Solidity => Some(&*SOL_QUERY_CACHE),
699        LangId::Scala => Some(&*SCALA_QUERY_CACHE),
700        LangId::Java => Some(&*JAVA_QUERY_CACHE),
701        LangId::Ruby => Some(&*RUBY_QUERY_CACHE),
702        LangId::Kotlin => Some(&*KOTLIN_QUERY_CACHE),
703        LangId::Swift => Some(&*SWIFT_QUERY_CACHE),
704        LangId::Php => Some(&*PHP_QUERY_CACHE),
705        LangId::Lua => Some(&*LUA_QUERY_CACHE),
706        LangId::Perl => Some(&*PERL_QUERY_CACHE),
707        LangId::Html | LangId::Markdown | LangId::Vue | LangId::Json => None,
708    };
709
710    query
711        .map(|result| {
712            result.as_ref().map_err(|message| AftError::ParseError {
713                message: message.clone(),
714            })
715        })
716        .transpose()
717}
718
719/// Cached parse result: mtime at parse time + the tree.
720struct CachedTree {
721    mtime: SystemTime,
722    size: u64,
723    content_hash: blake3::Hash,
724    tree: Tree,
725}
726
727/// Cached symbol extraction result: mtime at extraction time + symbols.
728#[derive(Clone)]
729struct CachedSymbols {
730    mtime: SystemTime,
731    size: u64,
732    content_hash: blake3::Hash,
733    symbols: Vec<Symbol>,
734}
735
736fn content_hash_for_source(source: &str) -> blake3::Hash {
737    if source.len() as u64 > cache_freshness::CONTENT_HASH_SIZE_CAP {
738        cache_freshness::zero_hash()
739    } else {
740        cache_freshness::hash_bytes(source.as_bytes())
741    }
742}
743
744fn cached_file_is_fresh(
745    path: &Path,
746    cached_mtime: SystemTime,
747    cached_size: u64,
748    cached_content_hash: blake3::Hash,
749    fallback_mtime: SystemTime,
750) -> bool {
751    let Ok(metadata) = std::fs::metadata(path) else {
752        return false;
753    };
754    let current_size = metadata.len();
755    if current_size != cached_size {
756        return false;
757    }
758
759    let current_mtime = metadata.modified().unwrap_or(fallback_mtime);
760    if current_size > cache_freshness::CONTENT_HASH_SIZE_CAP {
761        return current_mtime == cached_mtime;
762    }
763
764    matches!(
765        cache_freshness::hash_file_if_small(path, current_size),
766        Ok(Some(hash)) if hash == cached_content_hash
767    )
768}
769
770/// Shared symbol cache that can be pre-warmed in a background thread
771/// and read by all parser instances.
772#[derive(Clone, Default)]
773pub struct SymbolCache {
774    entries: HashMap<PathBuf, CachedSymbols>,
775    generation: u64,
776    project_root: Option<PathBuf>,
777}
778
779pub type SharedSymbolCache = Arc<RwLock<SymbolCache>>;
780
781impl SymbolCache {
782    pub fn new() -> Self {
783        Self {
784            entries: HashMap::new(),
785            generation: 0,
786            project_root: None,
787        }
788    }
789
790    /// Set the project root used for disk persistence.
791    pub fn set_project_root(&mut self, project_root: PathBuf) {
792        debug_assert!(project_root.is_absolute());
793        self.project_root = Some(project_root);
794    }
795
796    /// Set the project root only when the caller still belongs to the active
797    /// cache generation.
798    pub fn set_project_root_for_generation(
799        &mut self,
800        generation: u64,
801        project_root: PathBuf,
802    ) -> bool {
803        if self.generation != generation {
804            return false;
805        }
806        self.set_project_root(project_root);
807        true
808    }
809
810    /// Insert pre-warmed symbols for a file.
811    pub fn insert(
812        &mut self,
813        path: PathBuf,
814        mtime: SystemTime,
815        size: u64,
816        content_hash: blake3::Hash,
817        symbols: Vec<Symbol>,
818    ) {
819        self.entries.insert(
820            path,
821            CachedSymbols {
822                mtime,
823                size,
824                content_hash,
825                symbols,
826            },
827        );
828    }
829
830    /// Insert symbols only when the caller still belongs to the active cache generation.
831    pub fn insert_for_generation(
832        &mut self,
833        generation: u64,
834        path: PathBuf,
835        mtime: SystemTime,
836        size: u64,
837        content_hash: blake3::Hash,
838        symbols: Vec<Symbol>,
839    ) -> bool {
840        if self.generation != generation {
841            return false;
842        }
843        self.insert(path, mtime, size, content_hash, symbols);
844        true
845    }
846
847    /// Return cached symbols when the source file is still fresh.
848    pub fn get(&self, path: &Path, mtime: SystemTime) -> Option<Vec<Symbol>> {
849        self.entries.get(path).and_then(|cached| {
850            cached_file_is_fresh(path, cached.mtime, cached.size, cached.content_hash, mtime)
851                .then(|| cached.symbols.clone())
852        })
853    }
854
855    /// Whether the cache has a still-valid entry for the given file mtime.
856    pub fn contains_path_with_mtime(&self, path: &Path, mtime: SystemTime) -> bool {
857        self.entries
858            .get(path)
859            .is_some_and(|cached| cached.mtime == mtime)
860    }
861
862    /// Load valid symbol entries from disk, dropping only entries whose source file changed.
863    pub fn load_from_disk(
864        &mut self,
865        storage_dir: &Path,
866        project_key: &str,
867        current_root: &Path,
868    ) -> usize {
869        debug_assert!(current_root.is_absolute());
870        let Some(cache) = symbol_cache_disk::read_from_disk(storage_dir, project_key) else {
871            return 0;
872        };
873
874        self.project_root = Some(current_root.to_path_buf());
875        self.entries.clear();
876        let mut loaded = 0usize;
877
878        for entry in cache.entries {
879            let Some(path) =
880                crate::search_index::cached_path_under_root(current_root, &entry.relative_path)
881            else {
882                continue;
883            };
884            let cached_freshness = FileFreshness {
885                mtime: entry.mtime,
886                size: entry.size,
887                content_hash: entry.content_hash,
888            };
889            let mtime = match cache_freshness::verify_file(&path, &cached_freshness) {
890                FreshnessVerdict::HotFresh => entry.mtime,
891                FreshnessVerdict::ContentFresh { new_mtime, .. } => new_mtime,
892                FreshnessVerdict::Stale | FreshnessVerdict::Deleted => continue,
893            };
894
895            self.entries.insert(
896                path,
897                CachedSymbols {
898                    mtime,
899                    size: entry.size,
900                    content_hash: entry.content_hash,
901                    symbols: entry.symbols,
902                },
903            );
904            loaded += 1;
905        }
906
907        loaded
908    }
909
910    /// Load valid symbol entries from disk only when the caller still belongs
911    /// to the active cache generation.
912    pub fn load_from_disk_for_generation(
913        &mut self,
914        generation: u64,
915        storage_dir: &Path,
916        project_key: &str,
917        current_root: &Path,
918    ) -> usize {
919        if self.generation != generation {
920            return 0;
921        }
922        self.load_from_disk(storage_dir, project_key, current_root)
923    }
924
925    /// Invalidate cached symbols for a specific file.
926    pub fn invalidate(&mut self, path: &Path) {
927        self.entries.remove(path);
928    }
929
930    /// Clear all entries and advance the generation to ignore stale background writers.
931    pub fn reset(&mut self) -> u64 {
932        self.entries.clear();
933        self.project_root = None;
934        self.generation = self.generation.wrapping_add(1);
935        self.generation
936    }
937
938    /// Current generation token.
939    pub fn generation(&self) -> u64 {
940        self.generation
941    }
942
943    /// Whether the cache has an entry for a file.
944    pub fn contains_key(&self, path: &Path) -> bool {
945        self.entries.contains_key(path)
946    }
947
948    /// Number of cached entries.
949    pub fn len(&self) -> usize {
950        self.entries.len()
951    }
952
953    pub(crate) fn project_root(&self) -> Option<PathBuf> {
954        self.project_root.clone()
955    }
956
957    pub(crate) fn disk_entries(
958        &self,
959    ) -> Vec<(&PathBuf, SystemTime, u64, blake3::Hash, &Vec<Symbol>)> {
960        self.entries
961            .iter()
962            .filter_map(|(path, cached)| {
963                if cached.symbols.is_empty() {
964                    return None;
965                }
966                Some((
967                    path,
968                    cached.mtime,
969                    cached.size,
970                    cached.content_hash,
971                    &cached.symbols,
972                ))
973            })
974            .collect()
975    }
976}
977
978/// Core parsing engine. Handles language detection, parse tree caching,
979/// symbol table caching, and query pattern execution via tree-sitter.
980pub struct FileParser {
981    cache: HashMap<PathBuf, CachedTree>,
982    parsers: HashMap<LangId, Parser>,
983    symbol_cache: SharedSymbolCache,
984    symbol_cache_generation: Option<u64>,
985}
986
987impl FileParser {
988    /// Create a new `FileParser` with an empty parse cache.
989    pub fn new() -> Self {
990        Self::with_symbol_cache(Arc::new(RwLock::new(SymbolCache::new())))
991    }
992
993    /// Create a new `FileParser` backed by a shared symbol cache.
994    pub fn with_symbol_cache(symbol_cache: SharedSymbolCache) -> Self {
995        Self::with_symbol_cache_generation(symbol_cache, None)
996    }
997
998    /// Create a new `FileParser` backed by a shared symbol cache generation.
999    pub fn with_symbol_cache_generation(
1000        symbol_cache: SharedSymbolCache,
1001        symbol_cache_generation: Option<u64>,
1002    ) -> Self {
1003        Self {
1004            cache: HashMap::new(),
1005            parsers: HashMap::new(),
1006            symbol_cache,
1007            symbol_cache_generation,
1008        }
1009    }
1010
1011    fn parser_for(&mut self, lang: LangId) -> Result<&mut Parser, AftError> {
1012        use std::collections::hash_map::Entry;
1013
1014        match self.parsers.entry(lang) {
1015            Entry::Occupied(entry) => Ok(entry.into_mut()),
1016            Entry::Vacant(entry) => {
1017                let grammar = grammar_for(lang);
1018                let mut parser = Parser::new();
1019                parser.set_language(&grammar).map_err(|e| {
1020                    crate::slog_error!("grammar init failed for {:?}: {}", lang, e);
1021                    AftError::ParseError {
1022                        message: format!("grammar init failed for {:?}: {}", lang, e),
1023                    }
1024                })?;
1025                Ok(entry.insert(parser))
1026            }
1027        }
1028    }
1029
1030    /// Number of entries in the shared symbol cache.
1031    pub fn symbol_cache_len(&self) -> usize {
1032        self.symbol_cache
1033            .read()
1034            .map(|cache| cache.len())
1035            .unwrap_or(0)
1036    }
1037
1038    /// Shared symbol cache backing this parser.
1039    pub fn symbol_cache(&self) -> SharedSymbolCache {
1040        Arc::clone(&self.symbol_cache)
1041    }
1042
1043    /// Parse a file, returning the tree and detected language. Uses cache if
1044    /// the file hasn't been modified since last parse.
1045    pub fn parse(&mut self, path: &Path) -> Result<(&Tree, LangId), AftError> {
1046        let lang = detect_language(path).ok_or_else(|| AftError::InvalidRequest {
1047            message: format!(
1048                "unsupported file extension: {}",
1049                path.extension()
1050                    .and_then(|e| e.to_str())
1051                    .unwrap_or("<none>")
1052            ),
1053        })?;
1054
1055        let canon = path.to_path_buf();
1056        let current_mtime = std::fs::metadata(path)
1057            .and_then(|m| m.modified())
1058            .map_err(|e| AftError::FileNotFound {
1059                path: format!("{}: {}", path.display(), e),
1060            })?;
1061
1062        // Check cache validity. Mtime alone is not enough: editors and tests can
1063        // restore timestamps after changing file contents, so small files fall
1064        // back to a Blake3 content hash when size does not prove staleness.
1065        let needs_reparse = match self.cache.get(&canon) {
1066            Some(cached) => !cached_file_is_fresh(
1067                path,
1068                cached.mtime,
1069                cached.size,
1070                cached.content_hash,
1071                current_mtime,
1072            ),
1073            None => true,
1074        };
1075
1076        if needs_reparse {
1077            let source = std::fs::read_to_string(path).map_err(|e| AftError::FileNotFound {
1078                path: format!("{}: {}", path.display(), e),
1079            })?;
1080
1081            let tree = self.parser_for(lang)?.parse(&source, None).ok_or_else(|| {
1082                crate::slog_error!("parse failed for {}", path.display());
1083                AftError::ParseError {
1084                    message: format!("tree-sitter parse returned None for {}", path.display()),
1085                }
1086            })?;
1087
1088            self.cache.insert(
1089                canon.clone(),
1090                CachedTree {
1091                    mtime: current_mtime,
1092                    size: source.len() as u64,
1093                    content_hash: content_hash_for_source(&source),
1094                    tree,
1095                },
1096            );
1097        }
1098
1099        let cached = self.cache.get(&canon).ok_or_else(|| AftError::ParseError {
1100            message: format!("parser cache missing entry for {}", path.display()),
1101        })?;
1102        Ok((&cached.tree, lang))
1103    }
1104
1105    /// Like [`FileParser::parse`] but returns an owned `Tree` clone.
1106    ///
1107    /// Useful when the caller needs to hold the tree while also calling
1108    /// other mutable methods on this parser.
1109    pub fn parse_cloned(&mut self, path: &Path) -> Result<(Tree, LangId), AftError> {
1110        let (tree, lang) = self.parse(path)?;
1111        Ok((tree.clone(), lang))
1112    }
1113
1114    /// Extract symbols from a file using language-specific query patterns.
1115    /// Results are cached by `(path, mtime)` — subsequent calls for unchanged
1116    /// files return the cached symbol table without re-parsing.
1117    pub fn extract_symbols(&mut self, path: &Path) -> Result<Vec<Symbol>, AftError> {
1118        let canon = path.to_path_buf();
1119        let current_mtime = std::fs::metadata(path)
1120            .and_then(|m| m.modified())
1121            .map_err(|e| AftError::FileNotFound {
1122                path: format!("{}: {}", path.display(), e),
1123            })?;
1124
1125        // Return cached symbols if file hasn't changed.
1126        if let Some(symbols) = self
1127            .symbol_cache
1128            .read()
1129            .map_err(|_| AftError::ParseError {
1130                message: "symbol cache lock poisoned".to_string(),
1131            })?
1132            .get(&canon, current_mtime)
1133        {
1134            return Ok(symbols);
1135        }
1136
1137        let source = std::fs::read_to_string(path).map_err(|e| AftError::FileNotFound {
1138            path: format!("{}: {}", path.display(), e),
1139        })?;
1140        let size = source.len() as u64;
1141        let content_hash = content_hash_for_source(&source);
1142
1143        let symbols = {
1144            let (tree, lang) = self.parse(path)?;
1145            extract_symbols_from_tree(&source, tree, lang)?
1146        };
1147
1148        // Cache the result
1149        let mut symbol_cache = self
1150            .symbol_cache
1151            .write()
1152            .map_err(|_| AftError::ParseError {
1153                message: "symbol cache lock poisoned".to_string(),
1154            })?;
1155        if let Some(generation) = self.symbol_cache_generation {
1156            symbol_cache.insert_for_generation(
1157                generation,
1158                canon,
1159                current_mtime,
1160                size,
1161                content_hash,
1162                symbols.clone(),
1163            );
1164        } else {
1165            symbol_cache.insert(canon, current_mtime, size, content_hash, symbols.clone());
1166        }
1167
1168        Ok(symbols)
1169    }
1170
1171    /// Invalidate cached symbols for a specific file (e.g., after an edit).
1172    pub fn invalidate_symbols(&mut self, path: &Path) {
1173        if let Ok(mut symbol_cache) = self.symbol_cache.write() {
1174            symbol_cache.invalidate(path);
1175        }
1176        self.cache.remove(path);
1177    }
1178}
1179
1180/// Extract symbols from an already-parsed tree without reparsing.
1181///
1182/// Callers that already have a `tree_sitter::Tree` (e.g. callgraph::build_file_data)
1183/// should use this instead of `list_symbols(path)` to avoid the redundant parse.
1184pub fn extract_symbols_from_tree(
1185    source: &str,
1186    tree: &Tree,
1187    lang: LangId,
1188) -> Result<Vec<Symbol>, AftError> {
1189    let root = tree.root_node();
1190
1191    if lang == LangId::Html {
1192        return extract_html_symbols(source, &root);
1193    }
1194    if lang == LangId::Markdown {
1195        return extract_md_symbols(source, &root);
1196    }
1197    if lang == LangId::Vue {
1198        return extract_vue_symbols(source, &root);
1199    }
1200    if lang == LangId::Json {
1201        return extract_json_symbols(source, &root);
1202    }
1203
1204    let query = cached_query_for(lang)?.ok_or_else(|| AftError::InvalidRequest {
1205        message: format!("no query patterns implemented for {:?} yet", lang),
1206    })?;
1207
1208    match lang {
1209        LangId::TypeScript | LangId::Tsx => extract_ts_symbols(source, &root, query),
1210        LangId::JavaScript => extract_js_symbols(source, &root, query),
1211        LangId::Python => extract_py_symbols(source, &root, query),
1212        LangId::Rust => extract_rs_symbols(source, &root, query),
1213        LangId::Go => extract_go_symbols(source, &root, query),
1214        LangId::C => extract_c_symbols(source, &root, query),
1215        LangId::Cpp => extract_cpp_symbols(source, &root, query),
1216        LangId::Zig => extract_zig_symbols(source, &root, query),
1217        LangId::CSharp => extract_csharp_symbols(source, &root, query),
1218        LangId::Bash => extract_bash_symbols(source, &root, query),
1219        LangId::Solidity => extract_solidity_symbols(source, &root, query),
1220        LangId::Scala => extract_scala_symbols(source, &root, query),
1221        LangId::Java => extract_java_symbols(source, &root, query),
1222        LangId::Ruby => extract_ruby_symbols(source, &root, query),
1223        LangId::Kotlin => extract_kotlin_symbols(source, &root, query),
1224        LangId::Swift => extract_swift_symbols(source, &root, query),
1225        LangId::Php => extract_php_symbols(source, &root, query),
1226        LangId::Lua => extract_lua_symbols(source, &root, query),
1227        LangId::Perl => extract_perl_symbols(source, &root, query),
1228        LangId::Html | LangId::Markdown | LangId::Vue | LangId::Json => {
1229            unreachable!("handled before query lookup")
1230        }
1231    }
1232}
1233
1234/// Build a Range from a tree-sitter Node.
1235pub(crate) fn node_range(node: &Node) -> Range {
1236    let start = node.start_position();
1237    let end = node.end_position();
1238    Range {
1239        start_line: start.row as u32,
1240        start_col: start.column as u32,
1241        end_line: end.row as u32,
1242        end_col: end.column as u32,
1243    }
1244}
1245
1246/// Build a Range from a tree-sitter Node, expanding upward to include
1247/// preceding attributes, decorators, and doc comments that belong to the symbol.
1248///
1249/// This ensures that when agents edit/replace a symbol, they get the full
1250/// declaration including `#[test]`, `#[derive(...)]`, `/// doc`, `@decorator`, etc.
1251pub(crate) fn node_range_with_decorators(node: &Node, source: &str, lang: LangId) -> Range {
1252    if matches!(lang, LangId::Python) {
1253        if let Some(parent) = node.parent() {
1254            if parent.kind() == "decorated_definition" {
1255                return node_range(&parent);
1256            }
1257        }
1258    }
1259
1260    // TypeScript / JavaScript / TSX: `export function foo() {}` parses as
1261    // `export_statement > function_declaration`. The function/class/etc.
1262    // node alone starts AFTER the `export ` keyword, so symbol replace would
1263    // produce a syntactically-broken `export export function foo() {}` if the
1264    // agent's replacement content includes its own `export` (it almost always
1265    // does, because they're replacing the *declaration*). Walk up to the
1266    // export_statement so the range covers `export ...`/`export default ...`.
1267    if matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
1268        if let Some(parent) = node.parent() {
1269            if parent.kind() == "export_statement" {
1270                return node_range_with_decorators_inner(&parent, source, lang);
1271            }
1272        }
1273    }
1274
1275    node_range_with_decorators_inner(node, source, lang)
1276}
1277
1278/// Inner walk that handles preceding-sibling expansion (decorators, doc comments).
1279/// Split from `node_range_with_decorators` so the export-statement parent path can
1280/// recurse without re-checking the export wrapper.
1281fn node_range_with_decorators_inner(node: &Node, source: &str, lang: LangId) -> Range {
1282    let mut range = node_range(node);
1283
1284    let mut current = *node;
1285    while let Some(prev) = current.prev_sibling() {
1286        let kind = prev.kind();
1287        let should_include = match lang {
1288            LangId::Rust => {
1289                // Include #[...] attributes
1290                kind == "attribute_item"
1291                    // Include /// doc comments (but not regular // comments)
1292                    || (kind == "line_comment"
1293                        && node_text(source, &prev).starts_with("///"))
1294                    // Include /** ... */ doc comments
1295                    || (kind == "block_comment"
1296                        && node_text(source, &prev).starts_with("/**"))
1297            }
1298            LangId::TypeScript | LangId::Tsx | LangId::JavaScript => {
1299                // Include @decorator
1300                kind == "decorator"
1301                    // Include /** JSDoc */ comments
1302                    || (kind == "comment"
1303                        && node_text(source, &prev).starts_with("/**"))
1304            }
1305            LangId::Go | LangId::C | LangId::Cpp | LangId::Zig | LangId::CSharp | LangId::Bash => {
1306                // Include doc comments only if immediately above (no blank line gap)
1307                kind == "comment" && is_adjacent_line(&prev, &current, source)
1308            }
1309            LangId::Solidity
1310            | LangId::Scala
1311            | LangId::Java
1312            | LangId::Kotlin
1313            | LangId::Swift
1314            | LangId::Php => {
1315                // Include `///` doc comments and `/** */` doc blocks if immediately above
1316                let text = node_text(source, &prev);
1317                (kind == "comment" || kind == "line_comment" || kind == "block_comment")
1318                    && (text.starts_with("///") || text.starts_with("/**"))
1319                    && is_adjacent_line(&prev, &current, source)
1320            }
1321            LangId::Ruby | LangId::Lua => {
1322                // Include adjacent `#`/`---` style comments used as documentation.
1323                let text = node_text(source, &prev);
1324                kind == "comment"
1325                    && (text.starts_with('#') || text.starts_with("---"))
1326                    && is_adjacent_line(&prev, &current, source)
1327            }
1328            LangId::Perl => false,
1329            LangId::Python => {
1330                // Decorators are handled by decorated_definition capture
1331                false
1332            }
1333            LangId::Html | LangId::Markdown | LangId::Vue | LangId::Json => false,
1334        };
1335
1336        if should_include {
1337            range.start_line = prev.start_position().row as u32;
1338            range.start_col = prev.start_position().column as u32;
1339            current = prev;
1340        } else {
1341            break;
1342        }
1343    }
1344
1345    range
1346}
1347
1348/// Check if two nodes are on adjacent lines (no blank line between them).
1349fn is_adjacent_line(upper: &Node, lower: &Node, source: &str) -> bool {
1350    let upper_end = upper.end_position().row;
1351    let lower_start = lower.start_position().row;
1352
1353    if lower_start == 0 || lower_start <= upper_end {
1354        return true;
1355    }
1356
1357    // Check that there's no blank line between them
1358    let lines: Vec<&str> = source.lines().collect();
1359    for row in (upper_end + 1)..lower_start {
1360        if row < lines.len() && lines[row].trim().is_empty() {
1361            return false;
1362        }
1363    }
1364    true
1365}
1366
1367/// Extract the text of a node from source.
1368pub(crate) fn node_text<'a>(source: &'a str, node: &Node) -> &'a str {
1369    &source[node.byte_range()]
1370}
1371
1372fn lexical_declaration_has_function_value(node: &Node) -> bool {
1373    let mut cursor = node.walk();
1374    if !cursor.goto_first_child() {
1375        return false;
1376    }
1377
1378    loop {
1379        let child = cursor.node();
1380        if matches!(
1381            child.kind(),
1382            "arrow_function" | "function_expression" | "generator_function"
1383        ) {
1384            return true;
1385        }
1386
1387        if lexical_declaration_has_function_value(&child) {
1388            return true;
1389        }
1390
1391        if !cursor.goto_next_sibling() {
1392            break;
1393        }
1394    }
1395
1396    false
1397}
1398
1399/// Collect byte ranges of all export_statement nodes from query matches.
1400fn collect_export_ranges(source: &str, root: &Node, query: &Query) -> Vec<std::ops::Range<usize>> {
1401    let export_idx = query
1402        .capture_names()
1403        .iter()
1404        .position(|n| *n == "export.stmt");
1405    let export_idx = match export_idx {
1406        Some(i) => i as u32,
1407        None => return vec![],
1408    };
1409
1410    let mut cursor = QueryCursor::new();
1411    let mut ranges = Vec::new();
1412    let mut matches = cursor.matches(query, *root, source.as_bytes());
1413
1414    while let Some(m) = {
1415        matches.advance();
1416        matches.get()
1417    } {
1418        for cap in m.captures {
1419            if cap.index == export_idx {
1420                ranges.push(cap.node.byte_range());
1421            }
1422        }
1423    }
1424    ranges
1425}
1426
1427/// Check if a node's byte range is contained within any export statement.
1428fn is_exported(node: &Node, export_ranges: &[std::ops::Range<usize>]) -> bool {
1429    let r = node.byte_range();
1430    export_ranges
1431        .iter()
1432        .any(|er| er.start <= r.start && r.end <= er.end)
1433}
1434
1435fn collect_exported_symbol_names(source: &str, root: &Node) -> HashSet<String> {
1436    let mut exported = HashSet::new();
1437    collect_exported_symbol_names_inner(source, root, &mut exported);
1438    exported
1439}
1440
1441fn collect_exported_symbol_names_inner(source: &str, node: &Node, exported: &mut HashSet<String>) {
1442    if node.kind() == "export_statement" {
1443        collect_names_from_export_statement(source, node, exported);
1444    }
1445
1446    let mut cursor = node.walk();
1447    if !cursor.goto_first_child() {
1448        return;
1449    }
1450
1451    loop {
1452        let child = cursor.node();
1453        collect_exported_symbol_names_inner(source, &child, exported);
1454        if !cursor.goto_next_sibling() {
1455            break;
1456        }
1457    }
1458}
1459
1460fn collect_names_from_export_statement(source: &str, node: &Node, exported: &mut HashSet<String>) {
1461    let mut cursor = node.walk();
1462    if !cursor.goto_first_child() {
1463        return;
1464    }
1465
1466    let mut saw_default = false;
1467    loop {
1468        let child = cursor.node();
1469        match child.kind() {
1470            "default" => saw_default = true,
1471            "export_clause" => collect_names_from_export_clause(source, &child, exported),
1472            "identifier" | "type_identifier" | "property_identifier" if saw_default => {
1473                exported.insert(node_text(source, &child).to_string());
1474                return;
1475            }
1476            _ => {}
1477        }
1478        if !cursor.goto_next_sibling() {
1479            break;
1480        }
1481    }
1482}
1483
1484fn collect_names_from_export_clause(source: &str, node: &Node, exported: &mut HashSet<String>) {
1485    let mut cursor = node.walk();
1486    if !cursor.goto_first_child() {
1487        return;
1488    }
1489
1490    loop {
1491        let child = cursor.node();
1492        if child.kind() == "export_specifier" {
1493            if let Some(exported_name) = last_identifier_text(source, &child) {
1494                exported.insert(exported_name);
1495            }
1496        }
1497        if !cursor.goto_next_sibling() {
1498            break;
1499        }
1500    }
1501}
1502
1503fn last_identifier_text(source: &str, node: &Node) -> Option<String> {
1504    let mut cursor = node.walk();
1505    if !cursor.goto_first_child() {
1506        return None;
1507    }
1508
1509    let mut last = None;
1510    loop {
1511        let child = cursor.node();
1512        if matches!(
1513            child.kind(),
1514            "identifier"
1515                | "type_identifier"
1516                | "property_identifier"
1517                | "shorthand_property_identifier"
1518        ) {
1519            last = Some(node_text(source, &child).to_string());
1520        }
1521        if !cursor.goto_next_sibling() {
1522            break;
1523        }
1524    }
1525    last
1526}
1527
1528fn mark_named_exports(symbols: &mut [Symbol], exported_names: &HashSet<String>) {
1529    for symbol in symbols {
1530        if symbol.scope_chain.is_empty()
1531            && symbol.parent.is_none()
1532            && exported_names.contains(&symbol.name)
1533        {
1534            symbol.exported = true;
1535        }
1536    }
1537}
1538
1539/// Extract the first line of a node as its signature.
1540fn extract_signature(source: &str, node: &Node) -> String {
1541    let text = node_text(source, node);
1542    let first_line = text.lines().next().unwrap_or(text);
1543    // Trim trailing opening brace if present
1544    let trimmed = first_line.trim_end();
1545    let trimmed = trimmed.strip_suffix('{').unwrap_or(trimmed).trim_end();
1546    trimmed.to_string()
1547}
1548
1549/// Extract symbols from TypeScript / TSX source.
1550fn extract_ts_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1551    let lang = LangId::TypeScript;
1552    let capture_names = query.capture_names();
1553
1554    let export_ranges = collect_export_ranges(source, root, query);
1555    let exported_names = collect_exported_symbol_names(source, root);
1556
1557    let mut symbols = Vec::new();
1558    let mut cursor = QueryCursor::new();
1559    let mut matches = cursor.matches(query, *root, source.as_bytes());
1560
1561    while let Some(m) = {
1562        matches.advance();
1563        matches.get()
1564    } {
1565        // Determine what kind of match this is by looking at capture names
1566        let mut fn_name_node = None;
1567        let mut fn_def_node = None;
1568        let mut arrow_name_node = None;
1569        let mut arrow_def_node = None;
1570        let mut class_name_node = None;
1571        let mut class_def_node = None;
1572        let mut method_class_name_node = None;
1573        let mut method_name_node = None;
1574        let mut method_def_node = None;
1575        let mut interface_name_node = None;
1576        let mut interface_def_node = None;
1577        let mut enum_name_node = None;
1578        let mut enum_def_node = None;
1579        let mut type_alias_name_node = None;
1580        let mut type_alias_def_node = None;
1581        let mut var_name_node = None;
1582        let mut var_def_node = None;
1583
1584        for cap in m.captures {
1585            let Some(&name) = capture_names.get(cap.index as usize) else {
1586                continue;
1587            };
1588            match name {
1589                "fn.name" => fn_name_node = Some(cap.node),
1590                "fn.def" => fn_def_node = Some(cap.node),
1591                "arrow.name" => arrow_name_node = Some(cap.node),
1592                "arrow.def" => arrow_def_node = Some(cap.node),
1593                "class.name" => class_name_node = Some(cap.node),
1594                "class.def" => class_def_node = Some(cap.node),
1595                "method.class_name" => method_class_name_node = Some(cap.node),
1596                "method.name" => method_name_node = Some(cap.node),
1597                "method.def" => method_def_node = Some(cap.node),
1598                "interface.name" => interface_name_node = Some(cap.node),
1599                "interface.def" => interface_def_node = Some(cap.node),
1600                "enum.name" => enum_name_node = Some(cap.node),
1601                "enum.def" => enum_def_node = Some(cap.node),
1602                "type_alias.name" => type_alias_name_node = Some(cap.node),
1603                "type_alias.def" => type_alias_def_node = Some(cap.node),
1604                "var.name" => var_name_node = Some(cap.node),
1605                "var.def" => var_def_node = Some(cap.node),
1606                // var.value/var.decl removed — not needed
1607                _ => {}
1608            }
1609        }
1610
1611        // Function declaration
1612        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1613            symbols.push(Symbol {
1614                name: node_text(source, &name_node).to_string(),
1615                kind: SymbolKind::Function,
1616                range: node_range_with_decorators(&def_node, source, lang),
1617                signature: Some(extract_signature(source, &def_node)),
1618                scope_chain: vec![],
1619                exported: is_exported(&def_node, &export_ranges),
1620                parent: None,
1621            });
1622        }
1623
1624        // Arrow function
1625        if let (Some(name_node), Some(def_node)) = (arrow_name_node, arrow_def_node) {
1626            symbols.push(Symbol {
1627                name: node_text(source, &name_node).to_string(),
1628                kind: SymbolKind::Function,
1629                range: node_range_with_decorators(&def_node, source, lang),
1630                signature: Some(extract_signature(source, &def_node)),
1631                scope_chain: vec![],
1632                exported: is_exported(&def_node, &export_ranges),
1633                parent: None,
1634            });
1635        }
1636
1637        // Class declaration
1638        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
1639            symbols.push(Symbol {
1640                name: node_text(source, &name_node).to_string(),
1641                kind: SymbolKind::Class,
1642                range: node_range_with_decorators(&def_node, source, lang),
1643                signature: Some(extract_signature(source, &def_node)),
1644                scope_chain: vec![],
1645                exported: is_exported(&def_node, &export_ranges),
1646                parent: None,
1647            });
1648        }
1649
1650        // Method definition
1651        if let (Some(class_name_node), Some(name_node), Some(def_node)) =
1652            (method_class_name_node, method_name_node, method_def_node)
1653        {
1654            let class_name = node_text(source, &class_name_node).to_string();
1655            symbols.push(Symbol {
1656                name: node_text(source, &name_node).to_string(),
1657                kind: SymbolKind::Method,
1658                range: node_range_with_decorators(&def_node, source, lang),
1659                signature: Some(extract_signature(source, &def_node)),
1660                scope_chain: vec![class_name.clone()],
1661                exported: false, // methods inherit export from class
1662                parent: Some(class_name),
1663            });
1664        }
1665
1666        // Interface declaration
1667        if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
1668            symbols.push(Symbol {
1669                name: node_text(source, &name_node).to_string(),
1670                kind: SymbolKind::Interface,
1671                range: node_range_with_decorators(&def_node, source, lang),
1672                signature: Some(extract_signature(source, &def_node)),
1673                scope_chain: vec![],
1674                exported: is_exported(&def_node, &export_ranges),
1675                parent: None,
1676            });
1677        }
1678
1679        // Enum declaration
1680        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
1681            symbols.push(Symbol {
1682                name: node_text(source, &name_node).to_string(),
1683                kind: SymbolKind::Enum,
1684                range: node_range_with_decorators(&def_node, source, lang),
1685                signature: Some(extract_signature(source, &def_node)),
1686                scope_chain: vec![],
1687                exported: is_exported(&def_node, &export_ranges),
1688                parent: None,
1689            });
1690        }
1691
1692        // Type alias
1693        if let (Some(name_node), Some(def_node)) = (type_alias_name_node, type_alias_def_node) {
1694            symbols.push(Symbol {
1695                name: node_text(source, &name_node).to_string(),
1696                kind: SymbolKind::TypeAlias,
1697                range: node_range_with_decorators(&def_node, source, lang),
1698                signature: Some(extract_signature(source, &def_node)),
1699                scope_chain: vec![],
1700                exported: is_exported(&def_node, &export_ranges),
1701                parent: None,
1702            });
1703        }
1704
1705        // Top-level const/let variable declaration (not arrow functions — those are handled above)
1706        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
1707            // Only include module-scope variables (parent is program/export_statement, not inside a function)
1708            let is_top_level = def_node
1709                .parent()
1710                .map(|p| p.kind() == "program" || p.kind() == "export_statement")
1711                .unwrap_or(false);
1712            let is_function_like = lexical_declaration_has_function_value(&def_node);
1713            let name = node_text(source, &name_node).to_string();
1714            let already_captured = symbols.iter().any(|s| s.name == name);
1715            if is_top_level && !is_function_like && !already_captured {
1716                symbols.push(Symbol {
1717                    name,
1718                    kind: SymbolKind::Variable,
1719                    range: node_range_with_decorators(&def_node, source, lang),
1720                    signature: Some(extract_signature(source, &def_node)),
1721                    scope_chain: vec![],
1722                    exported: is_exported(&def_node, &export_ranges),
1723                    parent: None,
1724                });
1725            }
1726        }
1727    }
1728
1729    mark_named_exports(&mut symbols, &exported_names);
1730
1731    // Deduplicate: methods can appear as both class and method captures
1732    dedup_symbols(&mut symbols);
1733    Ok(symbols)
1734}
1735
1736/// Extract symbols from JavaScript source.
1737fn extract_js_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1738    let lang = LangId::JavaScript;
1739    let capture_names = query.capture_names();
1740
1741    let export_ranges = collect_export_ranges(source, root, query);
1742    let exported_names = collect_exported_symbol_names(source, root);
1743
1744    let mut symbols = Vec::new();
1745    let mut cursor = QueryCursor::new();
1746    let mut matches = cursor.matches(query, *root, source.as_bytes());
1747
1748    while let Some(m) = {
1749        matches.advance();
1750        matches.get()
1751    } {
1752        let mut fn_name_node = None;
1753        let mut fn_def_node = None;
1754        let mut arrow_name_node = None;
1755        let mut arrow_def_node = None;
1756        let mut class_name_node = None;
1757        let mut class_def_node = None;
1758        let mut method_class_name_node = None;
1759        let mut method_name_node = None;
1760        let mut method_def_node = None;
1761
1762        for cap in m.captures {
1763            let Some(&name) = capture_names.get(cap.index as usize) else {
1764                continue;
1765            };
1766            match name {
1767                "fn.name" => fn_name_node = Some(cap.node),
1768                "fn.def" => fn_def_node = Some(cap.node),
1769                "arrow.name" => arrow_name_node = Some(cap.node),
1770                "arrow.def" => arrow_def_node = Some(cap.node),
1771                "class.name" => class_name_node = Some(cap.node),
1772                "class.def" => class_def_node = Some(cap.node),
1773                "method.class_name" => method_class_name_node = Some(cap.node),
1774                "method.name" => method_name_node = Some(cap.node),
1775                "method.def" => method_def_node = Some(cap.node),
1776                _ => {}
1777            }
1778        }
1779
1780        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1781            symbols.push(Symbol {
1782                name: node_text(source, &name_node).to_string(),
1783                kind: SymbolKind::Function,
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_exported(&def_node, &export_ranges),
1788                parent: None,
1789            });
1790        }
1791
1792        if let (Some(name_node), Some(def_node)) = (arrow_name_node, arrow_def_node) {
1793            symbols.push(Symbol {
1794                name: node_text(source, &name_node).to_string(),
1795                kind: SymbolKind::Function,
1796                range: node_range_with_decorators(&def_node, source, lang),
1797                signature: Some(extract_signature(source, &def_node)),
1798                scope_chain: vec![],
1799                exported: is_exported(&def_node, &export_ranges),
1800                parent: None,
1801            });
1802        }
1803
1804        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
1805            symbols.push(Symbol {
1806                name: node_text(source, &name_node).to_string(),
1807                kind: SymbolKind::Class,
1808                range: node_range_with_decorators(&def_node, source, lang),
1809                signature: Some(extract_signature(source, &def_node)),
1810                scope_chain: vec![],
1811                exported: is_exported(&def_node, &export_ranges),
1812                parent: None,
1813            });
1814        }
1815
1816        if let (Some(class_name_node), Some(name_node), Some(def_node)) =
1817            (method_class_name_node, method_name_node, method_def_node)
1818        {
1819            let class_name = node_text(source, &class_name_node).to_string();
1820            symbols.push(Symbol {
1821                name: node_text(source, &name_node).to_string(),
1822                kind: SymbolKind::Method,
1823                range: node_range_with_decorators(&def_node, source, lang),
1824                signature: Some(extract_signature(source, &def_node)),
1825                scope_chain: vec![class_name.clone()],
1826                exported: false,
1827                parent: Some(class_name),
1828            });
1829        }
1830    }
1831
1832    mark_named_exports(&mut symbols, &exported_names);
1833    dedup_symbols(&mut symbols);
1834    Ok(symbols)
1835}
1836
1837/// Walk parent nodes to build a scope chain for Python symbols.
1838/// A function inside `class_definition > block` gets the class name in its scope.
1839fn py_scope_chain(node: &Node, source: &str) -> Vec<String> {
1840    let mut chain = Vec::new();
1841    let mut current = node.parent();
1842    while let Some(parent) = current {
1843        if parent.kind() == "class_definition" {
1844            if let Some(name_node) = parent.child_by_field_name("name") {
1845                chain.push(node_text(source, &name_node).to_string());
1846            }
1847        }
1848        current = parent.parent();
1849    }
1850    chain.reverse();
1851    chain
1852}
1853
1854/// Extract symbols from Python source.
1855fn extract_py_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1856    let lang = LangId::Python;
1857    let capture_names = query.capture_names();
1858
1859    let mut symbols = Vec::new();
1860    let mut cursor = QueryCursor::new();
1861    let mut matches = cursor.matches(query, *root, source.as_bytes());
1862
1863    // Track decorated definitions to avoid double-counting
1864    let mut decorated_fn_lines = std::collections::HashSet::new();
1865
1866    // First pass: collect decorated definition info
1867    {
1868        let mut cursor2 = QueryCursor::new();
1869        let mut matches2 = cursor2.matches(query, *root, source.as_bytes());
1870        while let Some(m) = {
1871            matches2.advance();
1872            matches2.get()
1873        } {
1874            let mut dec_def_node = None;
1875            let mut dec_decorator_node = None;
1876
1877            for cap in m.captures {
1878                let Some(&name) = capture_names.get(cap.index as usize) else {
1879                    continue;
1880                };
1881                match name {
1882                    "dec.def" => dec_def_node = Some(cap.node),
1883                    "dec.decorator" => dec_decorator_node = Some(cap.node),
1884                    _ => {}
1885                }
1886            }
1887
1888            if let (Some(def_node), Some(_dec_node)) = (dec_def_node, dec_decorator_node) {
1889                // Find the inner function_definition or class_definition
1890                let mut child_cursor = def_node.walk();
1891                if child_cursor.goto_first_child() {
1892                    loop {
1893                        let child = child_cursor.node();
1894                        if child.kind() == "function_definition"
1895                            || child.kind() == "class_definition"
1896                        {
1897                            decorated_fn_lines.insert(child.start_position().row);
1898                        }
1899                        if !child_cursor.goto_next_sibling() {
1900                            break;
1901                        }
1902                    }
1903                }
1904            }
1905        }
1906    }
1907
1908    while let Some(m) = {
1909        matches.advance();
1910        matches.get()
1911    } {
1912        let mut fn_name_node = None;
1913        let mut fn_def_node = None;
1914        let mut class_name_node = None;
1915        let mut class_def_node = None;
1916
1917        for cap in m.captures {
1918            let Some(&name) = capture_names.get(cap.index as usize) else {
1919                continue;
1920            };
1921            match name {
1922                "fn.name" => fn_name_node = Some(cap.node),
1923                "fn.def" => fn_def_node = Some(cap.node),
1924                "class.name" => class_name_node = Some(cap.node),
1925                "class.def" => class_def_node = Some(cap.node),
1926                _ => {}
1927            }
1928        }
1929
1930        // Function definition
1931        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1932            let scope = py_scope_chain(&def_node, source);
1933            let is_method = !scope.is_empty();
1934            let name = node_text(source, &name_node).to_string();
1935            // Skip __init__ and other dunders as separate symbols — they're methods
1936            let kind = if is_method {
1937                SymbolKind::Method
1938            } else {
1939                SymbolKind::Function
1940            };
1941
1942            // Build signature — include decorator if this is a decorated function
1943            let sig = if decorated_fn_lines.contains(&def_node.start_position().row) {
1944                // Find the decorated_definition parent to get decorator text
1945                let mut sig_parts = Vec::new();
1946                let mut parent = def_node.parent();
1947                while let Some(p) = parent {
1948                    if p.kind() == "decorated_definition" {
1949                        // Get decorator lines
1950                        let mut dc = p.walk();
1951                        if dc.goto_first_child() {
1952                            loop {
1953                                if dc.node().kind() == "decorator" {
1954                                    sig_parts.push(node_text(source, &dc.node()).to_string());
1955                                }
1956                                if !dc.goto_next_sibling() {
1957                                    break;
1958                                }
1959                            }
1960                        }
1961                        break;
1962                    }
1963                    parent = p.parent();
1964                }
1965                sig_parts.push(extract_signature(source, &def_node));
1966                Some(sig_parts.join("\n"))
1967            } else {
1968                Some(extract_signature(source, &def_node))
1969            };
1970
1971            symbols.push(Symbol {
1972                name,
1973                kind,
1974                range: node_range_with_decorators(&def_node, source, lang),
1975                signature: sig,
1976                scope_chain: scope.clone(),
1977                exported: false, // Python has no export concept
1978                parent: scope.last().cloned(),
1979            });
1980        }
1981
1982        // Class definition
1983        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
1984            let scope = py_scope_chain(&def_node, source);
1985
1986            // Build signature — include decorator if decorated
1987            let sig = if decorated_fn_lines.contains(&def_node.start_position().row) {
1988                let mut sig_parts = Vec::new();
1989                let mut parent = def_node.parent();
1990                while let Some(p) = parent {
1991                    if p.kind() == "decorated_definition" {
1992                        let mut dc = p.walk();
1993                        if dc.goto_first_child() {
1994                            loop {
1995                                if dc.node().kind() == "decorator" {
1996                                    sig_parts.push(node_text(source, &dc.node()).to_string());
1997                                }
1998                                if !dc.goto_next_sibling() {
1999                                    break;
2000                                }
2001                            }
2002                        }
2003                        break;
2004                    }
2005                    parent = p.parent();
2006                }
2007                sig_parts.push(extract_signature(source, &def_node));
2008                Some(sig_parts.join("\n"))
2009            } else {
2010                Some(extract_signature(source, &def_node))
2011            };
2012
2013            symbols.push(Symbol {
2014                name: node_text(source, &name_node).to_string(),
2015                kind: SymbolKind::Class,
2016                range: node_range_with_decorators(&def_node, source, lang),
2017                signature: sig,
2018                scope_chain: scope.clone(),
2019                exported: false,
2020                parent: scope.last().cloned(),
2021            });
2022        }
2023    }
2024
2025    dedup_symbols(&mut symbols);
2026    Ok(symbols)
2027}
2028
2029/// Extract symbols from Rust source.
2030/// Handles: free functions, struct, enum, trait (as Interface), impl methods with scope chains.
2031fn extract_rs_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2032    let lang = LangId::Rust;
2033    let capture_names = query.capture_names();
2034
2035    // Collect all visibility_modifier byte ranges first
2036    let mut vis_ranges: Vec<std::ops::Range<usize>> = Vec::new();
2037    {
2038        let vis_idx = capture_names.iter().position(|n| *n == "vis.mod");
2039        if let Some(idx) = vis_idx {
2040            let idx = idx as u32;
2041            let mut cursor = QueryCursor::new();
2042            let mut matches = cursor.matches(query, *root, source.as_bytes());
2043            while let Some(m) = {
2044                matches.advance();
2045                matches.get()
2046            } {
2047                for cap in m.captures {
2048                    if cap.index == idx {
2049                        vis_ranges.push(cap.node.byte_range());
2050                    }
2051                }
2052            }
2053        }
2054    }
2055
2056    let is_pub = |node: &Node| -> bool {
2057        // Check if the node has a visibility_modifier as a direct child
2058        let mut child_cursor = node.walk();
2059        if child_cursor.goto_first_child() {
2060            loop {
2061                if child_cursor.node().kind() == "visibility_modifier" {
2062                    return true;
2063                }
2064                if !child_cursor.goto_next_sibling() {
2065                    break;
2066                }
2067            }
2068        }
2069        false
2070    };
2071
2072    let mut symbols = Vec::new();
2073    let mut cursor = QueryCursor::new();
2074    let mut matches = cursor.matches(query, *root, source.as_bytes());
2075
2076    while let Some(m) = {
2077        matches.advance();
2078        matches.get()
2079    } {
2080        let mut fn_name_node = None;
2081        let mut fn_def_node = None;
2082        let mut struct_name_node = None;
2083        let mut struct_def_node = None;
2084        let mut enum_name_node = None;
2085        let mut enum_def_node = None;
2086        let mut trait_name_node = None;
2087        let mut trait_def_node = None;
2088        let mut impl_def_node = None;
2089
2090        for cap in m.captures {
2091            let Some(&name) = capture_names.get(cap.index as usize) else {
2092                continue;
2093            };
2094            match name {
2095                "fn.name" => fn_name_node = Some(cap.node),
2096                "fn.def" => fn_def_node = Some(cap.node),
2097                "struct.name" => struct_name_node = Some(cap.node),
2098                "struct.def" => struct_def_node = Some(cap.node),
2099                "enum.name" => enum_name_node = Some(cap.node),
2100                "enum.def" => enum_def_node = Some(cap.node),
2101                "trait.name" => trait_name_node = Some(cap.node),
2102                "trait.def" => trait_def_node = Some(cap.node),
2103                "impl.def" => impl_def_node = Some(cap.node),
2104                _ => {}
2105            }
2106        }
2107
2108        // Free function (not inside impl block — check parent)
2109        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2110            let parent = def_node.parent();
2111            let in_impl = parent
2112                .map(|p| p.kind() == "declaration_list")
2113                .unwrap_or(false);
2114            if !in_impl {
2115                symbols.push(Symbol {
2116                    name: node_text(source, &name_node).to_string(),
2117                    kind: SymbolKind::Function,
2118                    range: node_range_with_decorators(&def_node, source, lang),
2119                    signature: Some(extract_signature(source, &def_node)),
2120                    scope_chain: vec![],
2121                    exported: is_pub(&def_node),
2122                    parent: None,
2123                });
2124            }
2125        }
2126
2127        // Struct
2128        if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
2129            symbols.push(Symbol {
2130                name: node_text(source, &name_node).to_string(),
2131                kind: SymbolKind::Struct,
2132                range: node_range_with_decorators(&def_node, source, lang),
2133                signature: Some(extract_signature(source, &def_node)),
2134                scope_chain: vec![],
2135                exported: is_pub(&def_node),
2136                parent: None,
2137            });
2138        }
2139
2140        // Enum
2141        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
2142            symbols.push(Symbol {
2143                name: node_text(source, &name_node).to_string(),
2144                kind: SymbolKind::Enum,
2145                range: node_range_with_decorators(&def_node, source, lang),
2146                signature: Some(extract_signature(source, &def_node)),
2147                scope_chain: vec![],
2148                exported: is_pub(&def_node),
2149                parent: None,
2150            });
2151        }
2152
2153        // Trait (mapped to Interface kind)
2154        if let (Some(name_node), Some(def_node)) = (trait_name_node, trait_def_node) {
2155            symbols.push(Symbol {
2156                name: node_text(source, &name_node).to_string(),
2157                kind: SymbolKind::Interface,
2158                range: node_range_with_decorators(&def_node, source, lang),
2159                signature: Some(extract_signature(source, &def_node)),
2160                scope_chain: vec![],
2161                exported: is_pub(&def_node),
2162                parent: None,
2163            });
2164        }
2165
2166        // Impl block — extract methods from inside
2167        if let Some(impl_node) = impl_def_node {
2168            // Find the type name(s) from the impl
2169            // `impl TypeName { ... }` → scope = ["TypeName"]
2170            // `impl Trait for TypeName { ... }` → scope = ["Trait for TypeName"]
2171            let mut type_names: Vec<String> = Vec::new();
2172            let mut child_cursor = impl_node.walk();
2173            if child_cursor.goto_first_child() {
2174                loop {
2175                    let child = child_cursor.node();
2176                    if child.kind() == "type_identifier" || child.kind() == "generic_type" {
2177                        type_names.push(node_text(source, &child).to_string());
2178                    }
2179                    if !child_cursor.goto_next_sibling() {
2180                        break;
2181                    }
2182                }
2183            }
2184
2185            let scope_name = if type_names.len() >= 2 {
2186                // impl Trait for Type
2187                format!("{} for {}", type_names[0], type_names[1])
2188            } else if type_names.len() == 1 {
2189                type_names[0].clone()
2190            } else {
2191                String::new()
2192            };
2193
2194            let parent_name = type_names.last().cloned().unwrap_or_default();
2195
2196            // Find declaration_list and extract function_items
2197            let mut child_cursor = impl_node.walk();
2198            if child_cursor.goto_first_child() {
2199                loop {
2200                    let child = child_cursor.node();
2201                    if child.kind() == "declaration_list" {
2202                        let mut fn_cursor = child.walk();
2203                        if fn_cursor.goto_first_child() {
2204                            loop {
2205                                let fn_node = fn_cursor.node();
2206                                if fn_node.kind() == "function_item" {
2207                                    if let Some(name_node) = fn_node.child_by_field_name("name") {
2208                                        symbols.push(Symbol {
2209                                            name: node_text(source, &name_node).to_string(),
2210                                            kind: SymbolKind::Method,
2211                                            range: node_range_with_decorators(
2212                                                &fn_node, source, lang,
2213                                            ),
2214                                            signature: Some(extract_signature(source, &fn_node)),
2215                                            scope_chain: if scope_name.is_empty() {
2216                                                vec![]
2217                                            } else {
2218                                                vec![scope_name.clone()]
2219                                            },
2220                                            exported: is_pub(&fn_node),
2221                                            parent: if parent_name.is_empty() {
2222                                                None
2223                                            } else {
2224                                                Some(parent_name.clone())
2225                                            },
2226                                        });
2227                                    }
2228                                }
2229                                if !fn_cursor.goto_next_sibling() {
2230                                    break;
2231                                }
2232                            }
2233                        }
2234                    }
2235                    if !child_cursor.goto_next_sibling() {
2236                        break;
2237                    }
2238                }
2239            }
2240        }
2241    }
2242
2243    dedup_symbols(&mut symbols);
2244    Ok(symbols)
2245}
2246
2247/// Extract symbols from Go source.
2248/// Handles: functions, methods (with receiver scope chain), struct/interface types,
2249/// uppercase-first-letter export detection.
2250fn extract_go_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2251    let lang = LangId::Go;
2252    let capture_names = query.capture_names();
2253
2254    let is_go_exported = |name: &str| -> bool {
2255        name.chars()
2256            .next()
2257            .map(|c| c.is_uppercase())
2258            .unwrap_or(false)
2259    };
2260
2261    let mut symbols = Vec::new();
2262    let mut cursor = QueryCursor::new();
2263    let mut matches = cursor.matches(query, *root, source.as_bytes());
2264
2265    while let Some(m) = {
2266        matches.advance();
2267        matches.get()
2268    } {
2269        let mut fn_name_node = None;
2270        let mut fn_def_node = None;
2271        let mut method_name_node = None;
2272        let mut method_def_node = None;
2273        let mut type_name_node = None;
2274        let mut type_body_node = None;
2275        let mut type_def_node = None;
2276
2277        for cap in m.captures {
2278            let Some(&name) = capture_names.get(cap.index as usize) else {
2279                continue;
2280            };
2281            match name {
2282                "fn.name" => fn_name_node = Some(cap.node),
2283                "fn.def" => fn_def_node = Some(cap.node),
2284                "method.name" => method_name_node = Some(cap.node),
2285                "method.def" => method_def_node = Some(cap.node),
2286                "type.name" => type_name_node = Some(cap.node),
2287                "type.body" => type_body_node = Some(cap.node),
2288                "type.def" => type_def_node = Some(cap.node),
2289                _ => {}
2290            }
2291        }
2292
2293        // Function declaration
2294        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2295            let name = node_text(source, &name_node).to_string();
2296            symbols.push(Symbol {
2297                exported: is_go_exported(&name),
2298                name,
2299                kind: SymbolKind::Function,
2300                range: node_range_with_decorators(&def_node, source, lang),
2301                signature: Some(extract_signature(source, &def_node)),
2302                scope_chain: vec![],
2303                parent: None,
2304            });
2305        }
2306
2307        // Method declaration (with receiver)
2308        if let (Some(name_node), Some(def_node)) = (method_name_node, method_def_node) {
2309            let name = node_text(source, &name_node).to_string();
2310
2311            // Extract receiver type from the first parameter_list
2312            let receiver_type = extract_go_receiver_type(&def_node, source);
2313            let scope_chain = if let Some(ref rt) = receiver_type {
2314                vec![rt.clone()]
2315            } else {
2316                vec![]
2317            };
2318
2319            symbols.push(Symbol {
2320                exported: is_go_exported(&name),
2321                name,
2322                kind: SymbolKind::Method,
2323                range: node_range_with_decorators(&def_node, source, lang),
2324                signature: Some(extract_signature(source, &def_node)),
2325                scope_chain,
2326                parent: receiver_type,
2327            });
2328        }
2329
2330        // Type declarations (struct or interface)
2331        if let (Some(name_node), Some(body_node), Some(def_node)) =
2332            (type_name_node, type_body_node, type_def_node)
2333        {
2334            let name = node_text(source, &name_node).to_string();
2335            let kind = match body_node.kind() {
2336                "struct_type" => SymbolKind::Struct,
2337                "interface_type" => SymbolKind::Interface,
2338                _ => SymbolKind::TypeAlias,
2339            };
2340
2341            symbols.push(Symbol {
2342                exported: is_go_exported(&name),
2343                name,
2344                kind,
2345                range: node_range_with_decorators(&def_node, source, lang),
2346                signature: Some(extract_signature(source, &def_node)),
2347                scope_chain: vec![],
2348                parent: None,
2349            });
2350        }
2351    }
2352
2353    dedup_symbols(&mut symbols);
2354    Ok(symbols)
2355}
2356
2357/// Extract the receiver type from a Go method_declaration node.
2358/// e.g. `func (m *MyStruct) String()` → Some("MyStruct")
2359fn extract_go_receiver_type(method_node: &Node, source: &str) -> Option<String> {
2360    // The first parameter_list is the receiver
2361    let mut child_cursor = method_node.walk();
2362    if child_cursor.goto_first_child() {
2363        loop {
2364            let child = child_cursor.node();
2365            if child.kind() == "parameter_list" {
2366                // Walk into parameter_list to find type_identifier
2367                return find_type_identifier_recursive(&child, source);
2368            }
2369            if !child_cursor.goto_next_sibling() {
2370                break;
2371            }
2372        }
2373    }
2374    None
2375}
2376
2377fn split_scope_text(text: &str, separator: &str) -> Vec<String> {
2378    text.split(separator)
2379        .map(str::trim)
2380        .filter(|segment| !segment.is_empty())
2381        .map(ToString::to_string)
2382        .collect()
2383}
2384
2385fn last_scope_segment(text: &str, separator: &str) -> String {
2386    split_scope_text(text, separator)
2387        .pop()
2388        .unwrap_or_else(|| text.trim().to_string())
2389}
2390
2391fn zig_container_scope_chain(node: &Node, source: &str) -> Vec<String> {
2392    let mut chain = Vec::new();
2393    let mut current = node.parent();
2394
2395    while let Some(parent) = current {
2396        if matches!(
2397            parent.kind(),
2398            "struct_declaration" | "enum_declaration" | "union_declaration" | "opaque_declaration"
2399        ) {
2400            if let Some(container) = parent.parent() {
2401                if container.kind() == "variable_declaration" {
2402                    let mut cursor = container.walk();
2403                    if cursor.goto_first_child() {
2404                        loop {
2405                            let child = cursor.node();
2406                            if child.kind() == "identifier" {
2407                                chain.push(node_text(source, &child).to_string());
2408                                break;
2409                            }
2410                            if !cursor.goto_next_sibling() {
2411                                break;
2412                            }
2413                        }
2414                    }
2415                }
2416            }
2417        }
2418        current = parent.parent();
2419    }
2420
2421    chain.reverse();
2422    chain
2423}
2424
2425fn csharp_scope_chain(node: &Node, source: &str) -> Vec<String> {
2426    let mut chain = Vec::new();
2427    let mut current = node.parent();
2428
2429    while let Some(parent) = current {
2430        match parent.kind() {
2431            "namespace_declaration" | "file_scoped_namespace_declaration" => {
2432                if let Some(name_node) = parent.child_by_field_name("name") {
2433                    chain.push(node_text(source, &name_node).to_string());
2434                }
2435            }
2436            "class_declaration"
2437            | "interface_declaration"
2438            | "struct_declaration"
2439            | "record_declaration" => {
2440                if let Some(name_node) = parent.child_by_field_name("name") {
2441                    chain.push(node_text(source, &name_node).to_string());
2442                }
2443            }
2444            _ => {}
2445        }
2446        current = parent.parent();
2447    }
2448
2449    chain.reverse();
2450    chain
2451}
2452
2453fn cpp_parent_scope_chain(node: &Node, source: &str) -> Vec<String> {
2454    let mut chain = Vec::new();
2455    let mut current = node.parent();
2456
2457    while let Some(parent) = current {
2458        match parent.kind() {
2459            "namespace_definition" => {
2460                if let Some(name_node) = parent.child_by_field_name("name") {
2461                    chain.push(node_text(source, &name_node).to_string());
2462                }
2463            }
2464            "class_specifier" | "struct_specifier" => {
2465                if let Some(name_node) = parent.child_by_field_name("name") {
2466                    chain.push(last_scope_segment(node_text(source, &name_node), "::"));
2467                }
2468            }
2469            _ => {}
2470        }
2471        current = parent.parent();
2472    }
2473
2474    chain.reverse();
2475    chain
2476}
2477
2478fn template_signature(source: &str, template_node: &Node, item_node: &Node) -> String {
2479    format!(
2480        "{}\n{}",
2481        extract_signature(source, template_node),
2482        extract_signature(source, item_node)
2483    )
2484}
2485
2486/// Extract symbols from C source.
2487fn extract_c_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2488    let lang = LangId::C;
2489    let capture_names = query.capture_names();
2490
2491    let mut symbols = Vec::new();
2492    let mut cursor = QueryCursor::new();
2493    let mut matches = cursor.matches(query, *root, source.as_bytes());
2494
2495    while let Some(m) = {
2496        matches.advance();
2497        matches.get()
2498    } {
2499        let mut fn_name_node = None;
2500        let mut fn_def_node = None;
2501        let mut struct_name_node = None;
2502        let mut struct_def_node = None;
2503        let mut enum_name_node = None;
2504        let mut enum_def_node = None;
2505        let mut type_name_node = None;
2506        let mut type_def_node = None;
2507        let mut macro_name_node = None;
2508        let mut macro_def_node = None;
2509
2510        for cap in m.captures {
2511            let Some(&name) = capture_names.get(cap.index as usize) else {
2512                continue;
2513            };
2514            match name {
2515                "fn.name" => fn_name_node = Some(cap.node),
2516                "fn.def" => fn_def_node = Some(cap.node),
2517                "struct.name" => struct_name_node = Some(cap.node),
2518                "struct.def" => struct_def_node = Some(cap.node),
2519                "enum.name" => enum_name_node = Some(cap.node),
2520                "enum.def" => enum_def_node = Some(cap.node),
2521                "type.name" => type_name_node = Some(cap.node),
2522                "type.def" => type_def_node = Some(cap.node),
2523                "macro.name" => macro_name_node = Some(cap.node),
2524                "macro.def" => macro_def_node = Some(cap.node),
2525                _ => {}
2526            }
2527        }
2528
2529        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2530            symbols.push(Symbol {
2531                name: node_text(source, &name_node).to_string(),
2532                kind: SymbolKind::Function,
2533                range: node_range_with_decorators(&def_node, source, lang),
2534                signature: Some(extract_signature(source, &def_node)),
2535                scope_chain: vec![],
2536                exported: false,
2537                parent: None,
2538            });
2539        }
2540
2541        if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
2542            symbols.push(Symbol {
2543                name: node_text(source, &name_node).to_string(),
2544                kind: SymbolKind::Struct,
2545                range: node_range_with_decorators(&def_node, source, lang),
2546                signature: Some(extract_signature(source, &def_node)),
2547                scope_chain: vec![],
2548                exported: false,
2549                parent: None,
2550            });
2551        }
2552
2553        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
2554            symbols.push(Symbol {
2555                name: node_text(source, &name_node).to_string(),
2556                kind: SymbolKind::Enum,
2557                range: node_range_with_decorators(&def_node, source, lang),
2558                signature: Some(extract_signature(source, &def_node)),
2559                scope_chain: vec![],
2560                exported: false,
2561                parent: None,
2562            });
2563        }
2564
2565        if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
2566            symbols.push(Symbol {
2567                name: node_text(source, &name_node).to_string(),
2568                kind: SymbolKind::TypeAlias,
2569                range: node_range_with_decorators(&def_node, source, lang),
2570                signature: Some(extract_signature(source, &def_node)),
2571                scope_chain: vec![],
2572                exported: false,
2573                parent: None,
2574            });
2575        }
2576
2577        if let (Some(name_node), Some(def_node)) = (macro_name_node, macro_def_node) {
2578            symbols.push(Symbol {
2579                name: node_text(source, &name_node).to_string(),
2580                kind: SymbolKind::Variable,
2581                range: node_range(&def_node),
2582                signature: Some(extract_signature(source, &def_node)),
2583                scope_chain: vec![],
2584                exported: false,
2585                parent: None,
2586            });
2587        }
2588    }
2589
2590    dedup_symbols(&mut symbols);
2591    Ok(symbols)
2592}
2593
2594/// Extract symbols from C++ source.
2595fn extract_cpp_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2596    let lang = LangId::Cpp;
2597    let capture_names = query.capture_names();
2598
2599    let mut type_names = HashSet::new();
2600    {
2601        let mut cursor = QueryCursor::new();
2602        let mut matches = cursor.matches(query, *root, source.as_bytes());
2603        while let Some(m) = {
2604            matches.advance();
2605            matches.get()
2606        } {
2607            for cap in m.captures {
2608                let Some(&name) = capture_names.get(cap.index as usize) else {
2609                    continue;
2610                };
2611                match name {
2612                    "class.name"
2613                    | "struct.name"
2614                    | "template.class.name"
2615                    | "template.struct.name" => {
2616                        type_names.insert(last_scope_segment(node_text(source, &cap.node), "::"));
2617                    }
2618                    _ => {}
2619                }
2620            }
2621        }
2622    }
2623
2624    let mut symbols = Vec::new();
2625    let mut cursor = QueryCursor::new();
2626    let mut matches = cursor.matches(query, *root, source.as_bytes());
2627
2628    while let Some(m) = {
2629        matches.advance();
2630        matches.get()
2631    } {
2632        let mut fn_name_node = None;
2633        let mut fn_def_node = None;
2634        let mut method_name_node = None;
2635        let mut method_def_node = None;
2636        let mut qual_scope_node = None;
2637        let mut qual_name_node = None;
2638        let mut qual_def_node = None;
2639        let mut class_name_node = None;
2640        let mut class_def_node = None;
2641        let mut struct_name_node = None;
2642        let mut struct_def_node = None;
2643        let mut enum_name_node = None;
2644        let mut enum_def_node = None;
2645        let mut namespace_name_node = None;
2646        let mut namespace_def_node = None;
2647        let mut template_class_name_node = None;
2648        let mut template_class_def_node = None;
2649        let mut template_class_item_node = None;
2650        let mut template_struct_name_node = None;
2651        let mut template_struct_def_node = None;
2652        let mut template_struct_item_node = None;
2653        let mut template_fn_name_node = None;
2654        let mut template_fn_def_node = None;
2655        let mut template_fn_item_node = None;
2656        let mut template_qual_scope_node = None;
2657        let mut template_qual_name_node = None;
2658        let mut template_qual_def_node = None;
2659        let mut template_qual_item_node = None;
2660
2661        for cap in m.captures {
2662            let Some(&name) = capture_names.get(cap.index as usize) else {
2663                continue;
2664            };
2665            match name {
2666                "fn.name" => fn_name_node = Some(cap.node),
2667                "fn.def" => fn_def_node = Some(cap.node),
2668                "method.name" => method_name_node = Some(cap.node),
2669                "method.def" => method_def_node = Some(cap.node),
2670                "qual.scope" => qual_scope_node = Some(cap.node),
2671                "qual.name" => qual_name_node = Some(cap.node),
2672                "qual.def" => qual_def_node = Some(cap.node),
2673                "class.name" => class_name_node = Some(cap.node),
2674                "class.def" => class_def_node = Some(cap.node),
2675                "struct.name" => struct_name_node = Some(cap.node),
2676                "struct.def" => struct_def_node = Some(cap.node),
2677                "enum.name" => enum_name_node = Some(cap.node),
2678                "enum.def" => enum_def_node = Some(cap.node),
2679                "namespace.name" => namespace_name_node = Some(cap.node),
2680                "namespace.def" => namespace_def_node = Some(cap.node),
2681                "template.class.name" => template_class_name_node = Some(cap.node),
2682                "template.class.def" => template_class_def_node = Some(cap.node),
2683                "template.class.item" => template_class_item_node = Some(cap.node),
2684                "template.struct.name" => template_struct_name_node = Some(cap.node),
2685                "template.struct.def" => template_struct_def_node = Some(cap.node),
2686                "template.struct.item" => template_struct_item_node = Some(cap.node),
2687                "template.fn.name" => template_fn_name_node = Some(cap.node),
2688                "template.fn.def" => template_fn_def_node = Some(cap.node),
2689                "template.fn.item" => template_fn_item_node = Some(cap.node),
2690                "template.qual.scope" => template_qual_scope_node = Some(cap.node),
2691                "template.qual.name" => template_qual_name_node = Some(cap.node),
2692                "template.qual.def" => template_qual_def_node = Some(cap.node),
2693                "template.qual.item" => template_qual_item_node = Some(cap.node),
2694                _ => {}
2695            }
2696        }
2697
2698        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2699            let in_template = def_node
2700                .parent()
2701                .map(|parent| parent.kind() == "template_declaration")
2702                .unwrap_or(false);
2703            if !in_template {
2704                let scope_chain = cpp_parent_scope_chain(&def_node, source);
2705                symbols.push(Symbol {
2706                    name: node_text(source, &name_node).to_string(),
2707                    kind: SymbolKind::Function,
2708                    range: node_range_with_decorators(&def_node, source, lang),
2709                    signature: Some(extract_signature(source, &def_node)),
2710                    scope_chain: scope_chain.clone(),
2711                    exported: false,
2712                    parent: scope_chain.last().cloned(),
2713                });
2714            }
2715        }
2716
2717        if let (Some(name_node), Some(def_node)) = (method_name_node, method_def_node) {
2718            let scope_chain = cpp_parent_scope_chain(&def_node, source);
2719            symbols.push(Symbol {
2720                name: node_text(source, &name_node).to_string(),
2721                kind: SymbolKind::Method,
2722                range: node_range_with_decorators(&def_node, source, lang),
2723                signature: Some(extract_signature(source, &def_node)),
2724                scope_chain: scope_chain.clone(),
2725                exported: false,
2726                parent: scope_chain.last().cloned(),
2727            });
2728        }
2729
2730        if let (Some(scope_node), Some(name_node), Some(def_node)) =
2731            (qual_scope_node, qual_name_node, qual_def_node)
2732        {
2733            let in_template = def_node
2734                .parent()
2735                .map(|parent| parent.kind() == "template_declaration")
2736                .unwrap_or(false);
2737            if !in_template {
2738                let scope_text = node_text(source, &scope_node);
2739                let scope_chain = split_scope_text(scope_text, "::");
2740                let parent = scope_chain.last().cloned();
2741                let kind = if parent
2742                    .as_ref()
2743                    .map(|segment| type_names.contains(segment))
2744                    .unwrap_or(false)
2745                {
2746                    SymbolKind::Method
2747                } else {
2748                    SymbolKind::Function
2749                };
2750
2751                symbols.push(Symbol {
2752                    name: node_text(source, &name_node).to_string(),
2753                    kind,
2754                    range: node_range_with_decorators(&def_node, source, lang),
2755                    signature: Some(extract_signature(source, &def_node)),
2756                    scope_chain,
2757                    exported: false,
2758                    parent,
2759                });
2760            }
2761        }
2762
2763        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
2764            let in_template = def_node
2765                .parent()
2766                .map(|parent| parent.kind() == "template_declaration")
2767                .unwrap_or(false);
2768            if !in_template {
2769                let scope_chain = cpp_parent_scope_chain(&def_node, source);
2770                let name = last_scope_segment(node_text(source, &name_node), "::");
2771                symbols.push(Symbol {
2772                    name: name.clone(),
2773                    kind: SymbolKind::Class,
2774                    range: node_range_with_decorators(&def_node, source, lang),
2775                    signature: Some(extract_signature(source, &def_node)),
2776                    scope_chain: scope_chain.clone(),
2777                    exported: false,
2778                    parent: scope_chain.last().cloned(),
2779                });
2780            }
2781        }
2782
2783        if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
2784            let in_template = def_node
2785                .parent()
2786                .map(|parent| parent.kind() == "template_declaration")
2787                .unwrap_or(false);
2788            if !in_template {
2789                let scope_chain = cpp_parent_scope_chain(&def_node, source);
2790                let name = last_scope_segment(node_text(source, &name_node), "::");
2791                symbols.push(Symbol {
2792                    name: name.clone(),
2793                    kind: SymbolKind::Struct,
2794                    range: node_range_with_decorators(&def_node, source, lang),
2795                    signature: Some(extract_signature(source, &def_node)),
2796                    scope_chain: scope_chain.clone(),
2797                    exported: false,
2798                    parent: scope_chain.last().cloned(),
2799                });
2800            }
2801        }
2802
2803        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
2804            let scope_chain = cpp_parent_scope_chain(&def_node, source);
2805            let name = last_scope_segment(node_text(source, &name_node), "::");
2806            symbols.push(Symbol {
2807                name: name.clone(),
2808                kind: SymbolKind::Enum,
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)) = (namespace_name_node, namespace_def_node) {
2818            let scope_chain = cpp_parent_scope_chain(&def_node, source);
2819            symbols.push(Symbol {
2820                name: node_text(source, &name_node).to_string(),
2821                kind: SymbolKind::TypeAlias,
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), Some(item_node)) = (
2831            template_class_name_node,
2832            template_class_def_node,
2833            template_class_item_node,
2834        ) {
2835            let scope_chain = cpp_parent_scope_chain(&def_node, source);
2836            let name = last_scope_segment(node_text(source, &name_node), "::");
2837            symbols.push(Symbol {
2838                name: name.clone(),
2839                kind: SymbolKind::Class,
2840                range: node_range_with_decorators(&def_node, source, lang),
2841                signature: Some(template_signature(source, &def_node, &item_node)),
2842                scope_chain: scope_chain.clone(),
2843                exported: false,
2844                parent: scope_chain.last().cloned(),
2845            });
2846        }
2847
2848        if let (Some(name_node), Some(def_node), Some(item_node)) = (
2849            template_struct_name_node,
2850            template_struct_def_node,
2851            template_struct_item_node,
2852        ) {
2853            let scope_chain = cpp_parent_scope_chain(&def_node, source);
2854            let name = last_scope_segment(node_text(source, &name_node), "::");
2855            symbols.push(Symbol {
2856                name: name.clone(),
2857                kind: SymbolKind::Struct,
2858                range: node_range_with_decorators(&def_node, source, lang),
2859                signature: Some(template_signature(source, &def_node, &item_node)),
2860                scope_chain: scope_chain.clone(),
2861                exported: false,
2862                parent: scope_chain.last().cloned(),
2863            });
2864        }
2865
2866        if let (Some(name_node), Some(def_node), Some(item_node)) = (
2867            template_fn_name_node,
2868            template_fn_def_node,
2869            template_fn_item_node,
2870        ) {
2871            let scope_chain = cpp_parent_scope_chain(&def_node, source);
2872            symbols.push(Symbol {
2873                name: node_text(source, &name_node).to_string(),
2874                kind: SymbolKind::Function,
2875                range: node_range_with_decorators(&def_node, source, lang),
2876                signature: Some(template_signature(source, &def_node, &item_node)),
2877                scope_chain: scope_chain.clone(),
2878                exported: false,
2879                parent: scope_chain.last().cloned(),
2880            });
2881        }
2882
2883        if let (Some(scope_node), Some(name_node), Some(def_node), Some(item_node)) = (
2884            template_qual_scope_node,
2885            template_qual_name_node,
2886            template_qual_def_node,
2887            template_qual_item_node,
2888        ) {
2889            let scope_chain = split_scope_text(node_text(source, &scope_node), "::");
2890            let parent = scope_chain.last().cloned();
2891            let kind = if parent
2892                .as_ref()
2893                .map(|segment| type_names.contains(segment))
2894                .unwrap_or(false)
2895            {
2896                SymbolKind::Method
2897            } else {
2898                SymbolKind::Function
2899            };
2900
2901            symbols.push(Symbol {
2902                name: node_text(source, &name_node).to_string(),
2903                kind,
2904                range: node_range_with_decorators(&def_node, source, lang),
2905                signature: Some(template_signature(source, &def_node, &item_node)),
2906                scope_chain,
2907                exported: false,
2908                parent,
2909            });
2910        }
2911    }
2912
2913    dedup_symbols(&mut symbols);
2914    Ok(symbols)
2915}
2916
2917/// Extract symbols from Zig source.
2918fn extract_zig_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2919    let lang = LangId::Zig;
2920    let capture_names = query.capture_names();
2921
2922    let mut symbols = Vec::new();
2923    let mut cursor = QueryCursor::new();
2924    let mut matches = cursor.matches(query, *root, source.as_bytes());
2925
2926    while let Some(m) = {
2927        matches.advance();
2928        matches.get()
2929    } {
2930        let mut fn_name_node = None;
2931        let mut fn_def_node = None;
2932        let mut struct_name_node = None;
2933        let mut struct_def_node = None;
2934        let mut enum_name_node = None;
2935        let mut enum_def_node = None;
2936        let mut union_name_node = None;
2937        let mut union_def_node = None;
2938        let mut const_name_node = None;
2939        let mut const_def_node = None;
2940        let mut test_name_node = None;
2941        let mut test_def_node = None;
2942
2943        for cap in m.captures {
2944            let Some(&name) = capture_names.get(cap.index as usize) else {
2945                continue;
2946            };
2947            match name {
2948                "fn.name" => fn_name_node = Some(cap.node),
2949                "fn.def" => fn_def_node = Some(cap.node),
2950                "struct.name" => struct_name_node = Some(cap.node),
2951                "struct.def" => struct_def_node = Some(cap.node),
2952                "enum.name" => enum_name_node = Some(cap.node),
2953                "enum.def" => enum_def_node = Some(cap.node),
2954                "union.name" => union_name_node = Some(cap.node),
2955                "union.def" => union_def_node = Some(cap.node),
2956                "const.name" => const_name_node = Some(cap.node),
2957                "const.def" => const_def_node = Some(cap.node),
2958                "test.name" => test_name_node = Some(cap.node),
2959                "test.def" => test_def_node = Some(cap.node),
2960                _ => {}
2961            }
2962        }
2963
2964        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2965            let scope_chain = zig_container_scope_chain(&def_node, source);
2966            let kind = if scope_chain.is_empty() {
2967                SymbolKind::Function
2968            } else {
2969                SymbolKind::Method
2970            };
2971            symbols.push(Symbol {
2972                name: node_text(source, &name_node).to_string(),
2973                kind,
2974                range: node_range_with_decorators(&def_node, source, lang),
2975                signature: Some(extract_signature(source, &def_node)),
2976                scope_chain: scope_chain.clone(),
2977                exported: false,
2978                parent: scope_chain.last().cloned(),
2979            });
2980        }
2981
2982        if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
2983            symbols.push(Symbol {
2984                name: node_text(source, &name_node).to_string(),
2985                kind: SymbolKind::Struct,
2986                range: node_range_with_decorators(&def_node, source, lang),
2987                signature: Some(extract_signature(source, &def_node)),
2988                scope_chain: vec![],
2989                exported: false,
2990                parent: None,
2991            });
2992        }
2993
2994        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
2995            symbols.push(Symbol {
2996                name: node_text(source, &name_node).to_string(),
2997                kind: SymbolKind::Enum,
2998                range: node_range_with_decorators(&def_node, source, lang),
2999                signature: Some(extract_signature(source, &def_node)),
3000                scope_chain: vec![],
3001                exported: false,
3002                parent: None,
3003            });
3004        }
3005
3006        if let (Some(name_node), Some(def_node)) = (union_name_node, union_def_node) {
3007            symbols.push(Symbol {
3008                name: node_text(source, &name_node).to_string(),
3009                kind: SymbolKind::TypeAlias,
3010                range: node_range_with_decorators(&def_node, source, lang),
3011                signature: Some(extract_signature(source, &def_node)),
3012                scope_chain: vec![],
3013                exported: false,
3014                parent: None,
3015            });
3016        }
3017
3018        if let (Some(name_node), Some(def_node)) = (const_name_node, const_def_node) {
3019            let signature = extract_signature(source, &def_node);
3020            let is_container = signature.contains("= struct")
3021                || signature.contains("= enum")
3022                || signature.contains("= union")
3023                || signature.contains("= opaque");
3024            let is_const = signature.trim_start().starts_with("const ");
3025            let name = node_text(source, &name_node).to_string();
3026            let already_captured = symbols.iter().any(|symbol| symbol.name == name);
3027            if is_const && !is_container && !already_captured {
3028                symbols.push(Symbol {
3029                    name,
3030                    kind: SymbolKind::Variable,
3031                    range: node_range_with_decorators(&def_node, source, lang),
3032                    signature: Some(signature),
3033                    scope_chain: vec![],
3034                    exported: false,
3035                    parent: None,
3036                });
3037            }
3038        }
3039
3040        if let (Some(name_node), Some(def_node)) = (test_name_node, test_def_node) {
3041            let scope_chain = zig_container_scope_chain(&def_node, source);
3042            symbols.push(Symbol {
3043                name: node_text(source, &name_node).trim_matches('"').to_string(),
3044                kind: SymbolKind::Function,
3045                range: node_range_with_decorators(&def_node, source, lang),
3046                signature: Some(extract_signature(source, &def_node)),
3047                scope_chain: scope_chain.clone(),
3048                exported: false,
3049                parent: scope_chain.last().cloned(),
3050            });
3051        }
3052    }
3053
3054    dedup_symbols(&mut symbols);
3055    Ok(symbols)
3056}
3057
3058/// Extract symbols from C# source.
3059fn extract_csharp_symbols(
3060    source: &str,
3061    root: &Node,
3062    query: &Query,
3063) -> Result<Vec<Symbol>, AftError> {
3064    let lang = LangId::CSharp;
3065    let capture_names = query.capture_names();
3066
3067    let mut symbols = Vec::new();
3068    let mut cursor = QueryCursor::new();
3069    let mut matches = cursor.matches(query, *root, source.as_bytes());
3070
3071    while let Some(m) = {
3072        matches.advance();
3073        matches.get()
3074    } {
3075        let mut class_name_node = None;
3076        let mut class_def_node = None;
3077        let mut interface_name_node = None;
3078        let mut interface_def_node = None;
3079        let mut struct_name_node = None;
3080        let mut struct_def_node = None;
3081        let mut enum_name_node = None;
3082        let mut enum_def_node = None;
3083        let mut method_name_node = None;
3084        let mut method_def_node = None;
3085        let mut property_name_node = None;
3086        let mut property_def_node = None;
3087        let mut namespace_name_node = None;
3088        let mut namespace_def_node = None;
3089
3090        for cap in m.captures {
3091            let Some(&name) = capture_names.get(cap.index as usize) else {
3092                continue;
3093            };
3094            match name {
3095                "class.name" => class_name_node = Some(cap.node),
3096                "class.def" => class_def_node = Some(cap.node),
3097                "interface.name" => interface_name_node = Some(cap.node),
3098                "interface.def" => interface_def_node = Some(cap.node),
3099                "struct.name" => struct_name_node = Some(cap.node),
3100                "struct.def" => struct_def_node = Some(cap.node),
3101                "enum.name" => enum_name_node = Some(cap.node),
3102                "enum.def" => enum_def_node = Some(cap.node),
3103                "method.name" => method_name_node = Some(cap.node),
3104                "method.def" => method_def_node = Some(cap.node),
3105                "property.name" => property_name_node = Some(cap.node),
3106                "property.def" => property_def_node = Some(cap.node),
3107                "namespace.name" => namespace_name_node = Some(cap.node),
3108                "namespace.def" => namespace_def_node = Some(cap.node),
3109                _ => {}
3110            }
3111        }
3112
3113        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
3114            let scope_chain = csharp_scope_chain(&def_node, source);
3115            symbols.push(Symbol {
3116                name: node_text(source, &name_node).to_string(),
3117                kind: SymbolKind::Class,
3118                range: node_range_with_decorators(&def_node, source, lang),
3119                signature: Some(extract_signature(source, &def_node)),
3120                scope_chain: scope_chain.clone(),
3121                exported: false,
3122                parent: scope_chain.last().cloned(),
3123            });
3124        }
3125
3126        if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
3127            let scope_chain = csharp_scope_chain(&def_node, source);
3128            symbols.push(Symbol {
3129                name: node_text(source, &name_node).to_string(),
3130                kind: SymbolKind::Interface,
3131                range: node_range_with_decorators(&def_node, source, lang),
3132                signature: Some(extract_signature(source, &def_node)),
3133                scope_chain: scope_chain.clone(),
3134                exported: false,
3135                parent: scope_chain.last().cloned(),
3136            });
3137        }
3138
3139        if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
3140            let scope_chain = csharp_scope_chain(&def_node, source);
3141            symbols.push(Symbol {
3142                name: node_text(source, &name_node).to_string(),
3143                kind: SymbolKind::Struct,
3144                range: node_range_with_decorators(&def_node, source, lang),
3145                signature: Some(extract_signature(source, &def_node)),
3146                scope_chain: scope_chain.clone(),
3147                exported: false,
3148                parent: scope_chain.last().cloned(),
3149            });
3150        }
3151
3152        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
3153            let scope_chain = csharp_scope_chain(&def_node, source);
3154            symbols.push(Symbol {
3155                name: node_text(source, &name_node).to_string(),
3156                kind: SymbolKind::Enum,
3157                range: node_range_with_decorators(&def_node, source, lang),
3158                signature: Some(extract_signature(source, &def_node)),
3159                scope_chain: scope_chain.clone(),
3160                exported: false,
3161                parent: scope_chain.last().cloned(),
3162            });
3163        }
3164
3165        if let (Some(name_node), Some(def_node)) = (method_name_node, method_def_node) {
3166            let scope_chain = csharp_scope_chain(&def_node, source);
3167            symbols.push(Symbol {
3168                name: node_text(source, &name_node).to_string(),
3169                kind: SymbolKind::Method,
3170                range: node_range_with_decorators(&def_node, source, lang),
3171                signature: Some(extract_signature(source, &def_node)),
3172                scope_chain: scope_chain.clone(),
3173                exported: false,
3174                parent: scope_chain.last().cloned(),
3175            });
3176        }
3177
3178        if let (Some(name_node), Some(def_node)) = (property_name_node, property_def_node) {
3179            let scope_chain = csharp_scope_chain(&def_node, source);
3180            symbols.push(Symbol {
3181                name: node_text(source, &name_node).to_string(),
3182                kind: SymbolKind::Variable,
3183                range: node_range_with_decorators(&def_node, source, lang),
3184                signature: Some(extract_signature(source, &def_node)),
3185                scope_chain: scope_chain.clone(),
3186                exported: false,
3187                parent: scope_chain.last().cloned(),
3188            });
3189        }
3190
3191        if let (Some(name_node), Some(def_node)) = (namespace_name_node, namespace_def_node) {
3192            let scope_chain = csharp_scope_chain(&def_node, source);
3193            symbols.push(Symbol {
3194                name: node_text(source, &name_node).to_string(),
3195                kind: SymbolKind::TypeAlias,
3196                range: node_range_with_decorators(&def_node, source, lang),
3197                signature: Some(extract_signature(source, &def_node)),
3198                scope_chain: scope_chain.clone(),
3199                exported: false,
3200                parent: scope_chain.last().cloned(),
3201            });
3202        }
3203    }
3204
3205    dedup_symbols(&mut symbols);
3206    Ok(symbols)
3207}
3208
3209/// Recursively find the first type_identifier node in a subtree.
3210fn find_type_identifier_recursive(node: &Node, source: &str) -> Option<String> {
3211    if node.kind() == "type_identifier" {
3212        return Some(node_text(source, node).to_string());
3213    }
3214    let mut cursor = node.walk();
3215    if cursor.goto_first_child() {
3216        loop {
3217            if let Some(result) = find_type_identifier_recursive(&cursor.node(), source) {
3218                return Some(result);
3219            }
3220            if !cursor.goto_next_sibling() {
3221                break;
3222            }
3223        }
3224    }
3225    None
3226}
3227
3228/// Extract HTML headings (h1-h6) as symbols.
3229/// Each heading becomes a symbol with kind `Heading`, and its range covers
3230/// the element itself. Headings are nested based on their level.
3231fn extract_bash_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
3232    let lang = LangId::Bash;
3233    let capture_names = query.capture_names();
3234
3235    let mut symbols = Vec::new();
3236    let mut cursor = QueryCursor::new();
3237    let mut matches = cursor.matches(query, *root, source.as_bytes());
3238
3239    while let Some(m) = {
3240        matches.advance();
3241        matches.get()
3242    } {
3243        let mut fn_name_node = None;
3244        let mut fn_def_node = None;
3245
3246        for cap in m.captures {
3247            let Some(&name) = capture_names.get(cap.index as usize) else {
3248                continue;
3249            };
3250            match name {
3251                "fn.name" => fn_name_node = Some(cap.node),
3252                "fn.def" => fn_def_node = Some(cap.node),
3253                _ => {}
3254            }
3255        }
3256
3257        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
3258            symbols.push(Symbol {
3259                name: node_text(source, &name_node).to_string(),
3260                kind: SymbolKind::Function,
3261                range: node_range_with_decorators(&def_node, source, lang),
3262                signature: Some(extract_signature(source, &def_node)),
3263                scope_chain: vec![],
3264                exported: false,
3265                parent: None,
3266            });
3267        }
3268    }
3269
3270    Ok(symbols)
3271}
3272
3273/// Walk up from `node` and collect the names of any enclosing
3274/// contract / library / interface, outermost first.
3275fn solidity_scope_chain(node: &Node, source: &str) -> Vec<String> {
3276    let mut chain = Vec::new();
3277    let mut current = node.parent();
3278
3279    while let Some(parent) = current {
3280        match parent.kind() {
3281            "contract_declaration" | "library_declaration" | "interface_declaration" => {
3282                if let Some(name_node) = parent.child_by_field_name("name") {
3283                    chain.push(node_text(source, &name_node).to_string());
3284                }
3285            }
3286            _ => {}
3287        }
3288        current = parent.parent();
3289    }
3290
3291    chain.reverse();
3292    chain
3293}
3294
3295fn extract_solidity_symbols(
3296    source: &str,
3297    root: &Node,
3298    query: &Query,
3299) -> Result<Vec<Symbol>, AftError> {
3300    let lang = LangId::Solidity;
3301    let capture_names = query.capture_names();
3302
3303    let mut symbols = Vec::new();
3304    let mut cursor = QueryCursor::new();
3305    let mut matches = cursor.matches(query, *root, source.as_bytes());
3306
3307    while let Some(m) = {
3308        matches.advance();
3309        matches.get()
3310    } {
3311        let mut contract_name_node = None;
3312        let mut contract_def_node = None;
3313        let mut library_name_node = None;
3314        let mut library_def_node = None;
3315        let mut interface_name_node = None;
3316        let mut interface_def_node = None;
3317        let mut fn_name_node = None;
3318        let mut fn_def_node = None;
3319        let mut modifier_name_node = None;
3320        let mut modifier_def_node = None;
3321        let mut constructor_def_node = None;
3322        let mut event_name_node = None;
3323        let mut event_def_node = None;
3324        let mut error_name_node = None;
3325        let mut error_def_node = None;
3326        let mut struct_name_node = None;
3327        let mut struct_def_node = None;
3328        let mut enum_name_node = None;
3329        let mut enum_def_node = None;
3330        let mut var_name_node = None;
3331        let mut var_def_node = None;
3332
3333        for cap in m.captures {
3334            let Some(&name) = capture_names.get(cap.index as usize) else {
3335                continue;
3336            };
3337            match name {
3338                "contract.name" => contract_name_node = Some(cap.node),
3339                "contract.def" => contract_def_node = Some(cap.node),
3340                "library.name" => library_name_node = Some(cap.node),
3341                "library.def" => library_def_node = Some(cap.node),
3342                "interface.name" => interface_name_node = Some(cap.node),
3343                "interface.def" => interface_def_node = Some(cap.node),
3344                "fn.name" => fn_name_node = Some(cap.node),
3345                "fn.def" => fn_def_node = Some(cap.node),
3346                "modifier.name" => modifier_name_node = Some(cap.node),
3347                "modifier.def" => modifier_def_node = Some(cap.node),
3348                "constructor.def" => constructor_def_node = Some(cap.node),
3349                "event.name" => event_name_node = Some(cap.node),
3350                "event.def" => event_def_node = Some(cap.node),
3351                "error.name" => error_name_node = Some(cap.node),
3352                "error.def" => error_def_node = Some(cap.node),
3353                "struct.name" => struct_name_node = Some(cap.node),
3354                "struct.def" => struct_def_node = Some(cap.node),
3355                "enum.name" => enum_name_node = Some(cap.node),
3356                "enum.def" => enum_def_node = Some(cap.node),
3357                "var.name" => var_name_node = Some(cap.node),
3358                "var.def" => var_def_node = Some(cap.node),
3359                _ => {}
3360            }
3361        }
3362
3363        // Contract
3364        if let (Some(name_node), Some(def_node)) = (contract_name_node, contract_def_node) {
3365            symbols.push(Symbol {
3366                name: node_text(source, &name_node).to_string(),
3367                kind: SymbolKind::Class,
3368                range: node_range_with_decorators(&def_node, source, lang),
3369                signature: Some(extract_signature(source, &def_node)),
3370                scope_chain: vec![],
3371                exported: true,
3372                parent: None,
3373            });
3374        }
3375
3376        // Library (treated like a contract — class-shaped container)
3377        if let (Some(name_node), Some(def_node)) = (library_name_node, library_def_node) {
3378            symbols.push(Symbol {
3379                name: node_text(source, &name_node).to_string(),
3380                kind: SymbolKind::Class,
3381                range: node_range_with_decorators(&def_node, source, lang),
3382                signature: Some(extract_signature(source, &def_node)),
3383                scope_chain: vec![],
3384                exported: true,
3385                parent: None,
3386            });
3387        }
3388
3389        // Interface
3390        if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
3391            symbols.push(Symbol {
3392                name: node_text(source, &name_node).to_string(),
3393                kind: SymbolKind::Interface,
3394                range: node_range_with_decorators(&def_node, source, lang),
3395                signature: Some(extract_signature(source, &def_node)),
3396                scope_chain: vec![],
3397                exported: true,
3398                parent: None,
3399            });
3400        }
3401
3402        // Function — Method when inside a contract/library/interface, Function otherwise
3403        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
3404            let scope_chain = solidity_scope_chain(&def_node, source);
3405            let kind = if scope_chain.is_empty() {
3406                SymbolKind::Function
3407            } else {
3408                SymbolKind::Method
3409            };
3410            symbols.push(Symbol {
3411                name: node_text(source, &name_node).to_string(),
3412                kind,
3413                range: node_range_with_decorators(&def_node, source, lang),
3414                signature: Some(extract_signature(source, &def_node)),
3415                parent: scope_chain.last().cloned(),
3416                scope_chain,
3417                exported: true,
3418            });
3419        }
3420
3421        // Modifier — always inside a contract/library/interface, treat as Method
3422        if let (Some(name_node), Some(def_node)) = (modifier_name_node, modifier_def_node) {
3423            let scope_chain = solidity_scope_chain(&def_node, source);
3424            symbols.push(Symbol {
3425                name: node_text(source, &name_node).to_string(),
3426                kind: SymbolKind::Method,
3427                range: node_range_with_decorators(&def_node, source, lang),
3428                signature: Some(extract_signature(source, &def_node)),
3429                parent: scope_chain.last().cloned(),
3430                scope_chain,
3431                exported: true,
3432            });
3433        }
3434
3435        // Constructor — synthetic name "constructor", parent is the enclosing contract
3436        if let Some(def_node) = constructor_def_node {
3437            let scope_chain = solidity_scope_chain(&def_node, source);
3438            symbols.push(Symbol {
3439                name: "constructor".to_string(),
3440                kind: SymbolKind::Method,
3441                range: node_range_with_decorators(&def_node, source, lang),
3442                signature: Some(extract_signature(source, &def_node)),
3443                parent: scope_chain.last().cloned(),
3444                scope_chain,
3445                exported: true,
3446            });
3447        }
3448
3449        // Event
3450        if let (Some(name_node), Some(def_node)) = (event_name_node, event_def_node) {
3451            let scope_chain = solidity_scope_chain(&def_node, source);
3452            symbols.push(Symbol {
3453                name: node_text(source, &name_node).to_string(),
3454                kind: SymbolKind::Function,
3455                range: node_range_with_decorators(&def_node, source, lang),
3456                signature: Some(extract_signature(source, &def_node)),
3457                parent: scope_chain.last().cloned(),
3458                scope_chain,
3459                exported: true,
3460            });
3461        }
3462
3463        // Error (custom error declaration)
3464        if let (Some(name_node), Some(def_node)) = (error_name_node, error_def_node) {
3465            let scope_chain = solidity_scope_chain(&def_node, source);
3466            symbols.push(Symbol {
3467                name: node_text(source, &name_node).to_string(),
3468                kind: SymbolKind::TypeAlias,
3469                range: node_range_with_decorators(&def_node, source, lang),
3470                signature: Some(extract_signature(source, &def_node)),
3471                parent: scope_chain.last().cloned(),
3472                scope_chain,
3473                exported: true,
3474            });
3475        }
3476
3477        // Struct
3478        if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
3479            let scope_chain = solidity_scope_chain(&def_node, source);
3480            symbols.push(Symbol {
3481                name: node_text(source, &name_node).to_string(),
3482                kind: SymbolKind::Struct,
3483                range: node_range_with_decorators(&def_node, source, lang),
3484                signature: Some(extract_signature(source, &def_node)),
3485                parent: scope_chain.last().cloned(),
3486                scope_chain,
3487                exported: true,
3488            });
3489        }
3490
3491        // Enum
3492        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
3493            let scope_chain = solidity_scope_chain(&def_node, source);
3494            symbols.push(Symbol {
3495                name: node_text(source, &name_node).to_string(),
3496                kind: SymbolKind::Enum,
3497                range: node_range_with_decorators(&def_node, source, lang),
3498                signature: Some(extract_signature(source, &def_node)),
3499                parent: scope_chain.last().cloned(),
3500                scope_chain,
3501                exported: true,
3502            });
3503        }
3504
3505        // State variable
3506        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
3507            let scope_chain = solidity_scope_chain(&def_node, source);
3508            symbols.push(Symbol {
3509                name: node_text(source, &name_node).to_string(),
3510                kind: SymbolKind::Variable,
3511                range: node_range_with_decorators(&def_node, source, lang),
3512                signature: Some(extract_signature(source, &def_node)),
3513                parent: scope_chain.last().cloned(),
3514                scope_chain,
3515                exported: true,
3516            });
3517        }
3518    }
3519
3520    dedup_symbols(&mut symbols);
3521    Ok(symbols)
3522}
3523
3524fn extract_json_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
3525    let Some(value) = root.named_child(0) else {
3526        return Ok(Vec::new());
3527    };
3528
3529    if value.kind() != "object" {
3530        return Ok(Vec::new());
3531    }
3532
3533    let mut symbols = Vec::new();
3534    let mut cursor = value.walk();
3535    for child in value.named_children(&mut cursor) {
3536        if child.kind() != "pair" {
3537            continue;
3538        }
3539        let Some(key_node) = child.child_by_field_name("key") else {
3540            continue;
3541        };
3542        let name = node_text(source, &key_node).trim_matches('"').to_string();
3543        if name.is_empty() {
3544            continue;
3545        }
3546        symbols.push(Symbol {
3547            name,
3548            kind: SymbolKind::Variable,
3549            range: node_range_with_decorators(&child, source, LangId::Json),
3550            signature: None,
3551            scope_chain: vec![],
3552            exported: false,
3553            parent: None,
3554        });
3555    }
3556
3557    Ok(symbols)
3558}
3559
3560fn scala_scope_chain(node: &Node, source: &str) -> Vec<String> {
3561    let mut chain = Vec::new();
3562    let mut current = node.parent();
3563
3564    while let Some(parent) = current {
3565        match parent.kind() {
3566            "class_definition" | "object_definition" | "enum_definition" | "trait_definition" => {
3567                if let Some(name_node) = parent.child_by_field_name("name") {
3568                    chain.push(node_text(source, &name_node).to_string());
3569                }
3570            }
3571            _ => {}
3572        }
3573        current = parent.parent();
3574    }
3575
3576    chain.reverse();
3577    chain
3578}
3579
3580fn extract_scala_symbols(
3581    source: &str,
3582    root: &Node,
3583    query: &Query,
3584) -> Result<Vec<Symbol>, AftError> {
3585    let lang = LangId::Scala;
3586    let capture_names = query.capture_names();
3587
3588    let mut symbols = Vec::new();
3589    let mut cursor = QueryCursor::new();
3590    let mut matches = cursor.matches(query, *root, source.as_bytes());
3591
3592    while let Some(m) = {
3593        matches.advance();
3594        matches.get()
3595    } {
3596        let mut class_name_node = None;
3597        let mut class_def_node = None;
3598        let mut object_name_node = None;
3599        let mut object_def_node = None;
3600        let mut enum_name_node = None;
3601        let mut enum_def_node = None;
3602        let mut trait_name_node = None;
3603        let mut trait_def_node = None;
3604        let mut fn_name_node = None;
3605        let mut fn_def_node = None;
3606        let mut val_name_node = None;
3607        let mut val_def_node = None;
3608        let mut var_name_node = None;
3609        let mut var_def_node = None;
3610        let mut type_name_node = None;
3611        let mut type_def_node = None;
3612
3613        for cap in m.captures {
3614            let Some(&name) = capture_names.get(cap.index as usize) else {
3615                continue;
3616            };
3617            match name {
3618                "class.name" => class_name_node = Some(cap.node),
3619                "class.def" => class_def_node = Some(cap.node),
3620                "object.name" => object_name_node = Some(cap.node),
3621                "object.def" => object_def_node = Some(cap.node),
3622                "enum.name" => enum_name_node = Some(cap.node),
3623                "enum.def" => enum_def_node = Some(cap.node),
3624                "trait.name" => trait_name_node = Some(cap.node),
3625                "trait.def" => trait_def_node = Some(cap.node),
3626                "fn.name" => fn_name_node = Some(cap.node),
3627                "fn.def" => fn_def_node = Some(cap.node),
3628                "val.name" => val_name_node = Some(cap.node),
3629                "val.def" => val_def_node = Some(cap.node),
3630                "var.name" => var_name_node = Some(cap.node),
3631                "var.def" => var_def_node = Some(cap.node),
3632                "given.name" => val_name_node = Some(cap.node),
3633                "given.def" => val_def_node = Some(cap.node),
3634                "type.name" => type_name_node = Some(cap.node),
3635                "type.def" => type_def_node = Some(cap.node),
3636                _ => {}
3637            }
3638        }
3639
3640        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
3641            symbols.push(Symbol {
3642                name: node_text(source, &name_node).to_string(),
3643                kind: SymbolKind::Class,
3644                range: node_range_with_decorators(&def_node, source, lang),
3645                signature: Some(extract_signature(source, &def_node)),
3646                scope_chain: scala_scope_chain(&def_node, source),
3647                exported: true,
3648                parent: scala_scope_chain(&def_node, source).last().cloned(),
3649            });
3650        }
3651
3652        if let (Some(name_node), Some(def_node)) = (object_name_node, object_def_node) {
3653            symbols.push(Symbol {
3654                name: node_text(source, &name_node).to_string(),
3655                kind: SymbolKind::Class,
3656                range: node_range_with_decorators(&def_node, source, lang),
3657                signature: Some(extract_signature(source, &def_node)),
3658                scope_chain: scala_scope_chain(&def_node, source),
3659                exported: true,
3660                parent: scala_scope_chain(&def_node, source).last().cloned(),
3661            });
3662        }
3663
3664        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
3665            symbols.push(Symbol {
3666                name: node_text(source, &name_node).to_string(),
3667                kind: SymbolKind::Class,
3668                range: node_range_with_decorators(&def_node, source, lang),
3669                signature: Some(extract_signature(source, &def_node)),
3670                scope_chain: scala_scope_chain(&def_node, source),
3671                exported: true,
3672                parent: scala_scope_chain(&def_node, source).last().cloned(),
3673            });
3674        }
3675
3676        if let (Some(name_node), Some(def_node)) = (trait_name_node, trait_def_node) {
3677            symbols.push(Symbol {
3678                name: node_text(source, &name_node).to_string(),
3679                kind: SymbolKind::Interface,
3680                range: node_range_with_decorators(&def_node, source, lang),
3681                signature: Some(extract_signature(source, &def_node)),
3682                scope_chain: scala_scope_chain(&def_node, source),
3683                exported: true,
3684                parent: scala_scope_chain(&def_node, source).last().cloned(),
3685            });
3686        }
3687
3688        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
3689            let scope_chain = scala_scope_chain(&def_node, source);
3690            let kind = if scope_chain.is_empty() {
3691                SymbolKind::Function
3692            } else {
3693                SymbolKind::Method
3694            };
3695            symbols.push(Symbol {
3696                name: node_text(source, &name_node).to_string(),
3697                kind,
3698                range: node_range_with_decorators(&def_node, source, lang),
3699                signature: Some(extract_signature(source, &def_node)),
3700                parent: scope_chain.last().cloned(),
3701                scope_chain,
3702                exported: true,
3703            });
3704        }
3705
3706        if let (Some(name_node), Some(def_node)) = (val_name_node, val_def_node) {
3707            let scope_chain = scala_scope_chain(&def_node, source);
3708            symbols.push(Symbol {
3709                name: node_text(source, &name_node).to_string(),
3710                kind: SymbolKind::Variable,
3711                range: node_range_with_decorators(&def_node, source, lang),
3712                signature: Some(extract_signature(source, &def_node)),
3713                parent: scope_chain.last().cloned(),
3714                scope_chain,
3715                exported: true,
3716            });
3717        }
3718
3719        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
3720            let scope_chain = scala_scope_chain(&def_node, source);
3721            symbols.push(Symbol {
3722                name: node_text(source, &name_node).to_string(),
3723                kind: SymbolKind::Variable,
3724                range: node_range_with_decorators(&def_node, source, lang),
3725                signature: Some(extract_signature(source, &def_node)),
3726                parent: scope_chain.last().cloned(),
3727                scope_chain,
3728                exported: true,
3729            });
3730        }
3731
3732        if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
3733            let scope_chain = scala_scope_chain(&def_node, source);
3734            symbols.push(Symbol {
3735                name: node_text(source, &name_node).to_string(),
3736                kind: SymbolKind::TypeAlias,
3737                range: node_range_with_decorators(&def_node, source, lang),
3738                signature: Some(extract_signature(source, &def_node)),
3739                parent: scope_chain.last().cloned(),
3740                scope_chain,
3741                exported: true,
3742            });
3743        }
3744    }
3745
3746    dedup_symbols(&mut symbols);
3747    Ok(symbols)
3748}
3749
3750fn child_text_by_field_or_kind(
3751    node: &Node,
3752    source: &str,
3753    field_name: &str,
3754    kinds: &[&str],
3755) -> Option<String> {
3756    if let Some(name_node) = node.child_by_field_name(field_name) {
3757        return Some(node_text(source, &name_node).to_string());
3758    }
3759
3760    let mut cursor = node.walk();
3761    if !cursor.goto_first_child() {
3762        return None;
3763    }
3764
3765    loop {
3766        let child = cursor.node();
3767        if kinds.contains(&child.kind()) {
3768            return Some(node_text(source, &child).to_string());
3769        }
3770        if !cursor.goto_next_sibling() {
3771            break;
3772        }
3773    }
3774
3775    None
3776}
3777
3778fn push_captured_symbol(
3779    symbols: &mut Vec<Symbol>,
3780    source: &str,
3781    lang: LangId,
3782    name_node: Node,
3783    def_node: Node,
3784    kind: SymbolKind,
3785    scope_chain: Vec<String>,
3786    exported: bool,
3787) {
3788    symbols.push(Symbol {
3789        name: node_text(source, &name_node).to_string(),
3790        kind,
3791        range: node_range_with_decorators(&def_node, source, lang),
3792        signature: Some(extract_signature(source, &def_node)),
3793        parent: scope_chain.last().cloned(),
3794        scope_chain,
3795        exported,
3796    });
3797}
3798
3799fn java_scope_chain(node: &Node, source: &str) -> Vec<String> {
3800    let mut chain = Vec::new();
3801    let mut current = node.parent();
3802
3803    while let Some(parent) = current {
3804        if matches!(
3805            parent.kind(),
3806            "class_declaration"
3807                | "interface_declaration"
3808                | "annotation_type_declaration"
3809                | "enum_declaration"
3810                | "record_declaration"
3811        ) {
3812            if let Some(name_node) = parent.child_by_field_name("name") {
3813                chain.push(node_text(source, &name_node).to_string());
3814            }
3815        }
3816        current = parent.parent();
3817    }
3818
3819    chain.reverse();
3820    chain
3821}
3822
3823fn extract_java_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
3824    let lang = LangId::Java;
3825    let capture_names = query.capture_names();
3826    let mut symbols = Vec::new();
3827    let mut cursor = QueryCursor::new();
3828    let mut matches = cursor.matches(query, *root, source.as_bytes());
3829
3830    while let Some(m) = {
3831        matches.advance();
3832        matches.get()
3833    } {
3834        let mut class_name_node = None;
3835        let mut class_def_node = None;
3836        let mut interface_name_node = None;
3837        let mut interface_def_node = None;
3838        let mut enum_name_node = None;
3839        let mut enum_def_node = None;
3840        let mut struct_name_node = None;
3841        let mut struct_def_node = None;
3842        let mut fn_name_node = None;
3843        let mut fn_def_node = None;
3844        let mut var_name_node = None;
3845        let mut var_def_node = None;
3846
3847        for cap in m.captures {
3848            let Some(&name) = capture_names.get(cap.index as usize) else {
3849                continue;
3850            };
3851            match name {
3852                "class.name" => class_name_node = Some(cap.node),
3853                "class.def" => class_def_node = Some(cap.node),
3854                "interface.name" => interface_name_node = Some(cap.node),
3855                "interface.def" => interface_def_node = Some(cap.node),
3856                "enum.name" => enum_name_node = Some(cap.node),
3857                "enum.def" => enum_def_node = Some(cap.node),
3858                "struct.name" => struct_name_node = Some(cap.node),
3859                "struct.def" => struct_def_node = Some(cap.node),
3860                "fn.name" => fn_name_node = Some(cap.node),
3861                "fn.def" => fn_def_node = Some(cap.node),
3862                "var.name" => var_name_node = Some(cap.node),
3863                "var.def" => var_def_node = Some(cap.node),
3864                _ => {}
3865            }
3866        }
3867
3868        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
3869            push_captured_symbol(
3870                &mut symbols,
3871                source,
3872                lang,
3873                name_node,
3874                def_node,
3875                SymbolKind::Class,
3876                java_scope_chain(&def_node, source),
3877                true,
3878            );
3879        }
3880        if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
3881            push_captured_symbol(
3882                &mut symbols,
3883                source,
3884                lang,
3885                name_node,
3886                def_node,
3887                SymbolKind::Interface,
3888                java_scope_chain(&def_node, source),
3889                true,
3890            );
3891        }
3892        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
3893            push_captured_symbol(
3894                &mut symbols,
3895                source,
3896                lang,
3897                name_node,
3898                def_node,
3899                SymbolKind::Enum,
3900                java_scope_chain(&def_node, source),
3901                true,
3902            );
3903        }
3904        if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
3905            push_captured_symbol(
3906                &mut symbols,
3907                source,
3908                lang,
3909                name_node,
3910                def_node,
3911                SymbolKind::Struct,
3912                java_scope_chain(&def_node, source),
3913                true,
3914            );
3915        }
3916        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
3917            let scope_chain = java_scope_chain(&def_node, source);
3918            let kind = if scope_chain.is_empty() {
3919                SymbolKind::Function
3920            } else {
3921                SymbolKind::Method
3922            };
3923            push_captured_symbol(
3924                &mut symbols,
3925                source,
3926                lang,
3927                name_node,
3928                def_node,
3929                kind,
3930                scope_chain,
3931                true,
3932            );
3933        }
3934        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
3935            push_captured_symbol(
3936                &mut symbols,
3937                source,
3938                lang,
3939                name_node,
3940                def_node,
3941                SymbolKind::Variable,
3942                java_scope_chain(&def_node, source),
3943                true,
3944            );
3945        }
3946    }
3947
3948    dedup_symbols(&mut symbols);
3949    Ok(symbols)
3950}
3951
3952fn ruby_scope_chain(node: &Node, source: &str) -> Vec<String> {
3953    let mut chain = Vec::new();
3954    let mut current = node.parent();
3955
3956    while let Some(parent) = current {
3957        if matches!(parent.kind(), "class" | "module") {
3958            if let Some(name_node) = parent.child_by_field_name("name") {
3959                chain.push(node_text(source, &name_node).to_string());
3960            }
3961        }
3962        current = parent.parent();
3963    }
3964
3965    chain.reverse();
3966    chain
3967}
3968
3969fn extract_ruby_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
3970    let lang = LangId::Ruby;
3971    let capture_names = query.capture_names();
3972    let mut symbols = Vec::new();
3973    let mut cursor = QueryCursor::new();
3974    let mut matches = cursor.matches(query, *root, source.as_bytes());
3975
3976    while let Some(m) = {
3977        matches.advance();
3978        matches.get()
3979    } {
3980        let mut module_name_node = None;
3981        let mut module_def_node = None;
3982        let mut class_name_node = None;
3983        let mut class_def_node = None;
3984        let mut fn_name_node = None;
3985        let mut fn_def_node = None;
3986        let mut var_name_node = None;
3987        let mut var_def_node = None;
3988
3989        for cap in m.captures {
3990            let Some(&name) = capture_names.get(cap.index as usize) else {
3991                continue;
3992            };
3993            match name {
3994                "module.name" => module_name_node = Some(cap.node),
3995                "module.def" => module_def_node = Some(cap.node),
3996                "class.name" => class_name_node = Some(cap.node),
3997                "class.def" => class_def_node = Some(cap.node),
3998                "fn.name" => fn_name_node = Some(cap.node),
3999                "fn.def" => fn_def_node = Some(cap.node),
4000                "var.name" => var_name_node = Some(cap.node),
4001                "var.def" => var_def_node = Some(cap.node),
4002                _ => {}
4003            }
4004        }
4005
4006        if let (Some(name_node), Some(def_node)) = (module_name_node, module_def_node) {
4007            push_captured_symbol(
4008                &mut symbols,
4009                source,
4010                lang,
4011                name_node,
4012                def_node,
4013                SymbolKind::Class,
4014                ruby_scope_chain(&def_node, source),
4015                true,
4016            );
4017        }
4018        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
4019            push_captured_symbol(
4020                &mut symbols,
4021                source,
4022                lang,
4023                name_node,
4024                def_node,
4025                SymbolKind::Class,
4026                ruby_scope_chain(&def_node, source),
4027                true,
4028            );
4029        }
4030        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
4031            let scope_chain = ruby_scope_chain(&def_node, source);
4032            let kind = if scope_chain.is_empty() {
4033                SymbolKind::Function
4034            } else {
4035                SymbolKind::Method
4036            };
4037            push_captured_symbol(
4038                &mut symbols,
4039                source,
4040                lang,
4041                name_node,
4042                def_node,
4043                kind,
4044                scope_chain,
4045                true,
4046            );
4047        }
4048        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
4049            push_captured_symbol(
4050                &mut symbols,
4051                source,
4052                lang,
4053                name_node,
4054                def_node,
4055                SymbolKind::Variable,
4056                ruby_scope_chain(&def_node, source),
4057                true,
4058            );
4059        }
4060    }
4061
4062    dedup_symbols(&mut symbols);
4063    Ok(symbols)
4064}
4065
4066fn kotlin_scope_chain(node: &Node, source: &str) -> Vec<String> {
4067    let mut chain = Vec::new();
4068    let mut current = node.parent();
4069
4070    while let Some(parent) = current {
4071        if matches!(parent.kind(), "class_declaration" | "object_declaration") {
4072            if let Some(name) =
4073                child_text_by_field_or_kind(&parent, source, "name", &["type_identifier"])
4074            {
4075                chain.push(name);
4076            }
4077        }
4078        current = parent.parent();
4079    }
4080
4081    chain.reverse();
4082    chain
4083}
4084
4085fn extract_kotlin_symbols(
4086    source: &str,
4087    root: &Node,
4088    query: &Query,
4089) -> Result<Vec<Symbol>, AftError> {
4090    let lang = LangId::Kotlin;
4091    let capture_names = query.capture_names();
4092    let mut symbols = Vec::new();
4093    let mut cursor = QueryCursor::new();
4094    let mut matches = cursor.matches(query, *root, source.as_bytes());
4095
4096    while let Some(m) = {
4097        matches.advance();
4098        matches.get()
4099    } {
4100        let mut class_name_node = None;
4101        let mut class_def_node = None;
4102        let mut object_name_node = None;
4103        let mut object_def_node = None;
4104        let mut fn_name_node = None;
4105        let mut fn_def_node = None;
4106        let mut var_name_node = None;
4107        let mut var_def_node = None;
4108        let mut type_name_node = None;
4109        let mut type_def_node = None;
4110
4111        for cap in m.captures {
4112            let Some(&name) = capture_names.get(cap.index as usize) else {
4113                continue;
4114            };
4115            match name {
4116                "class.name" => class_name_node = Some(cap.node),
4117                "class.def" => class_def_node = Some(cap.node),
4118                "object.name" => object_name_node = Some(cap.node),
4119                "object.def" => object_def_node = Some(cap.node),
4120                "fn.name" => fn_name_node = Some(cap.node),
4121                "fn.def" => fn_def_node = Some(cap.node),
4122                "var.name" => var_name_node = Some(cap.node),
4123                "var.def" => var_def_node = Some(cap.node),
4124                "type.name" => type_name_node = Some(cap.node),
4125                "type.def" => type_def_node = Some(cap.node),
4126                _ => {}
4127            }
4128        }
4129
4130        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
4131            push_captured_symbol(
4132                &mut symbols,
4133                source,
4134                lang,
4135                name_node,
4136                def_node,
4137                SymbolKind::Class,
4138                kotlin_scope_chain(&def_node, source),
4139                true,
4140            );
4141        }
4142        if let (Some(name_node), Some(def_node)) = (object_name_node, object_def_node) {
4143            push_captured_symbol(
4144                &mut symbols,
4145                source,
4146                lang,
4147                name_node,
4148                def_node,
4149                SymbolKind::Class,
4150                kotlin_scope_chain(&def_node, source),
4151                true,
4152            );
4153        }
4154        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
4155            let scope_chain = kotlin_scope_chain(&def_node, source);
4156            let kind = if scope_chain.is_empty() {
4157                SymbolKind::Function
4158            } else {
4159                SymbolKind::Method
4160            };
4161            push_captured_symbol(
4162                &mut symbols,
4163                source,
4164                lang,
4165                name_node,
4166                def_node,
4167                kind,
4168                scope_chain,
4169                true,
4170            );
4171        }
4172        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
4173            push_captured_symbol(
4174                &mut symbols,
4175                source,
4176                lang,
4177                name_node,
4178                def_node,
4179                SymbolKind::Variable,
4180                kotlin_scope_chain(&def_node, source),
4181                true,
4182            );
4183        }
4184        if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
4185            push_captured_symbol(
4186                &mut symbols,
4187                source,
4188                lang,
4189                name_node,
4190                def_node,
4191                SymbolKind::TypeAlias,
4192                kotlin_scope_chain(&def_node, source),
4193                true,
4194            );
4195        }
4196    }
4197
4198    dedup_symbols(&mut symbols);
4199    Ok(symbols)
4200}
4201
4202fn swift_scope_chain(node: &Node, source: &str) -> Vec<String> {
4203    let mut chain = Vec::new();
4204    let mut current = node.parent();
4205
4206    while let Some(parent) = current {
4207        if matches!(parent.kind(), "class_declaration" | "protocol_declaration") {
4208            if let Some(name_node) = parent.child_by_field_name("name") {
4209                chain.push(node_text(source, &name_node).to_string());
4210            }
4211        }
4212        current = parent.parent();
4213    }
4214
4215    chain.reverse();
4216    chain
4217}
4218
4219fn swift_type_kind(source: &str, node: &Node) -> SymbolKind {
4220    let signature = extract_signature(source, node);
4221    if signature.starts_with("struct ") {
4222        SymbolKind::Struct
4223    } else if signature.starts_with("enum ") {
4224        SymbolKind::Enum
4225    } else {
4226        SymbolKind::Class
4227    }
4228}
4229
4230fn extract_swift_symbols(
4231    source: &str,
4232    root: &Node,
4233    query: &Query,
4234) -> Result<Vec<Symbol>, AftError> {
4235    let lang = LangId::Swift;
4236    let capture_names = query.capture_names();
4237    let mut symbols = Vec::new();
4238    let mut cursor = QueryCursor::new();
4239    let mut matches = cursor.matches(query, *root, source.as_bytes());
4240
4241    while let Some(m) = {
4242        matches.advance();
4243        matches.get()
4244    } {
4245        let mut class_name_node = None;
4246        let mut class_def_node = None;
4247        let mut interface_name_node = None;
4248        let mut interface_def_node = None;
4249        let mut fn_name_node = None;
4250        let mut fn_def_node = None;
4251        let mut var_name_node = None;
4252        let mut var_def_node = None;
4253        let mut type_name_node = None;
4254        let mut type_def_node = None;
4255
4256        for cap in m.captures {
4257            let Some(&name) = capture_names.get(cap.index as usize) else {
4258                continue;
4259            };
4260            match name {
4261                "class.name" => class_name_node = Some(cap.node),
4262                "class.def" => class_def_node = Some(cap.node),
4263                "interface.name" => interface_name_node = Some(cap.node),
4264                "interface.def" => interface_def_node = Some(cap.node),
4265                "fn.name" => fn_name_node = Some(cap.node),
4266                "fn.def" => fn_def_node = Some(cap.node),
4267                "var.name" => var_name_node = Some(cap.node),
4268                "var.def" => var_def_node = Some(cap.node),
4269                "type.name" => type_name_node = Some(cap.node),
4270                "type.def" => type_def_node = Some(cap.node),
4271                _ => {}
4272            }
4273        }
4274
4275        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
4276            let kind = swift_type_kind(source, &def_node);
4277            push_captured_symbol(
4278                &mut symbols,
4279                source,
4280                lang,
4281                name_node,
4282                def_node,
4283                kind,
4284                swift_scope_chain(&def_node, source),
4285                true,
4286            );
4287        }
4288        if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
4289            push_captured_symbol(
4290                &mut symbols,
4291                source,
4292                lang,
4293                name_node,
4294                def_node,
4295                SymbolKind::Interface,
4296                swift_scope_chain(&def_node, source),
4297                true,
4298            );
4299        }
4300        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
4301            let scope_chain = swift_scope_chain(&def_node, source);
4302            let kind = if scope_chain.is_empty() {
4303                SymbolKind::Function
4304            } else {
4305                SymbolKind::Method
4306            };
4307            push_captured_symbol(
4308                &mut symbols,
4309                source,
4310                lang,
4311                name_node,
4312                def_node,
4313                kind,
4314                scope_chain,
4315                true,
4316            );
4317        }
4318        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
4319            push_captured_symbol(
4320                &mut symbols,
4321                source,
4322                lang,
4323                name_node,
4324                def_node,
4325                SymbolKind::Variable,
4326                swift_scope_chain(&def_node, source),
4327                true,
4328            );
4329        }
4330        if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
4331            push_captured_symbol(
4332                &mut symbols,
4333                source,
4334                lang,
4335                name_node,
4336                def_node,
4337                SymbolKind::TypeAlias,
4338                swift_scope_chain(&def_node, source),
4339                true,
4340            );
4341        }
4342    }
4343
4344    dedup_symbols(&mut symbols);
4345    Ok(symbols)
4346}
4347
4348fn php_scope_chain(node: &Node, source: &str) -> Vec<String> {
4349    let mut chain = Vec::new();
4350    let mut current = node.parent();
4351
4352    while let Some(parent) = current {
4353        match parent.kind() {
4354            "namespace_definition"
4355            | "class_declaration"
4356            | "interface_declaration"
4357            | "trait_declaration"
4358            | "enum_declaration" => {
4359                if let Some(name_node) = parent.child_by_field_name("name") {
4360                    chain.push(node_text(source, &name_node).to_string());
4361                }
4362            }
4363            _ => {}
4364        }
4365        current = parent.parent();
4366    }
4367
4368    chain.reverse();
4369    chain
4370}
4371
4372fn extract_php_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
4373    let lang = LangId::Php;
4374    let capture_names = query.capture_names();
4375    let mut symbols = Vec::new();
4376    let mut cursor = QueryCursor::new();
4377    let mut matches = cursor.matches(query, *root, source.as_bytes());
4378
4379    while let Some(m) = {
4380        matches.advance();
4381        matches.get()
4382    } {
4383        let mut namespace_name_node = None;
4384        let mut namespace_def_node = None;
4385        let mut class_name_node = None;
4386        let mut class_def_node = None;
4387        let mut interface_name_node = None;
4388        let mut interface_def_node = None;
4389        let mut trait_name_node = None;
4390        let mut trait_def_node = None;
4391        let mut enum_name_node = None;
4392        let mut enum_def_node = None;
4393        let mut fn_name_node = None;
4394        let mut fn_def_node = None;
4395        let mut var_name_node = None;
4396        let mut var_def_node = None;
4397
4398        for cap in m.captures {
4399            let Some(&name) = capture_names.get(cap.index as usize) else {
4400                continue;
4401            };
4402            match name {
4403                "namespace.name" => namespace_name_node = Some(cap.node),
4404                "namespace.def" => namespace_def_node = Some(cap.node),
4405                "class.name" => class_name_node = Some(cap.node),
4406                "class.def" => class_def_node = Some(cap.node),
4407                "interface.name" => interface_name_node = Some(cap.node),
4408                "interface.def" => interface_def_node = Some(cap.node),
4409                "trait.name" => trait_name_node = Some(cap.node),
4410                "trait.def" => trait_def_node = Some(cap.node),
4411                "enum.name" => enum_name_node = Some(cap.node),
4412                "enum.def" => enum_def_node = Some(cap.node),
4413                "fn.name" => fn_name_node = Some(cap.node),
4414                "fn.def" => fn_def_node = Some(cap.node),
4415                "var.name" => var_name_node = Some(cap.node),
4416                "var.def" => var_def_node = Some(cap.node),
4417                _ => {}
4418            }
4419        }
4420
4421        if let (Some(name_node), Some(def_node)) = (namespace_name_node, namespace_def_node) {
4422            push_captured_symbol(
4423                &mut symbols,
4424                source,
4425                lang,
4426                name_node,
4427                def_node,
4428                SymbolKind::Class,
4429                php_scope_chain(&def_node, source),
4430                true,
4431            );
4432        }
4433        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
4434            push_captured_symbol(
4435                &mut symbols,
4436                source,
4437                lang,
4438                name_node,
4439                def_node,
4440                SymbolKind::Class,
4441                php_scope_chain(&def_node, source),
4442                true,
4443            );
4444        }
4445        if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
4446            push_captured_symbol(
4447                &mut symbols,
4448                source,
4449                lang,
4450                name_node,
4451                def_node,
4452                SymbolKind::Interface,
4453                php_scope_chain(&def_node, source),
4454                true,
4455            );
4456        }
4457        if let (Some(name_node), Some(def_node)) = (trait_name_node, trait_def_node) {
4458            push_captured_symbol(
4459                &mut symbols,
4460                source,
4461                lang,
4462                name_node,
4463                def_node,
4464                SymbolKind::Interface,
4465                php_scope_chain(&def_node, source),
4466                true,
4467            );
4468        }
4469        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
4470            push_captured_symbol(
4471                &mut symbols,
4472                source,
4473                lang,
4474                name_node,
4475                def_node,
4476                SymbolKind::Enum,
4477                php_scope_chain(&def_node, source),
4478                true,
4479            );
4480        }
4481        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
4482            let scope_chain = php_scope_chain(&def_node, source);
4483            let kind = if scope_chain.is_empty() || def_node.kind() == "function_definition" {
4484                SymbolKind::Function
4485            } else {
4486                SymbolKind::Method
4487            };
4488            push_captured_symbol(
4489                &mut symbols,
4490                source,
4491                lang,
4492                name_node,
4493                def_node,
4494                kind,
4495                scope_chain,
4496                true,
4497            );
4498        }
4499        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
4500            push_captured_symbol(
4501                &mut symbols,
4502                source,
4503                lang,
4504                name_node,
4505                def_node,
4506                SymbolKind::Variable,
4507                php_scope_chain(&def_node, source),
4508                true,
4509            );
4510        }
4511    }
4512
4513    dedup_symbols(&mut symbols);
4514    Ok(symbols)
4515}
4516
4517fn lua_scope_chain(node: &Node, source: &str) -> Vec<String> {
4518    let mut chain = Vec::new();
4519
4520    if node.kind() == "function_declaration" {
4521        if let Some(name_node) = node.child_by_field_name("name") {
4522            match name_node.kind() {
4523                "dot_index_expression" | "method_index_expression" => {
4524                    if let Some(table_node) = name_node.child_by_field_name("table") {
4525                        chain.push(node_text(source, &table_node).to_string());
4526                    }
4527                }
4528                _ => {}
4529            }
4530        }
4531    }
4532
4533    chain
4534}
4535
4536fn extract_lua_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
4537    let lang = LangId::Lua;
4538    let capture_names = query.capture_names();
4539    let mut symbols = Vec::new();
4540    let mut cursor = QueryCursor::new();
4541    let mut matches = cursor.matches(query, *root, source.as_bytes());
4542
4543    while let Some(m) = {
4544        matches.advance();
4545        matches.get()
4546    } {
4547        let mut fn_name_node = None;
4548        let mut fn_def_node = None;
4549        let mut var_name_node = None;
4550        let mut var_def_node = None;
4551
4552        for cap in m.captures {
4553            let Some(&name) = capture_names.get(cap.index as usize) else {
4554                continue;
4555            };
4556            match name {
4557                "fn.name" => fn_name_node = Some(cap.node),
4558                "fn.def" => fn_def_node = Some(cap.node),
4559                "var.name" => var_name_node = Some(cap.node),
4560                "var.def" => var_def_node = Some(cap.node),
4561                _ => {}
4562            }
4563        }
4564
4565        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
4566            let scope_chain = lua_scope_chain(&def_node, source);
4567            let kind = if scope_chain.is_empty() {
4568                SymbolKind::Function
4569            } else {
4570                SymbolKind::Method
4571            };
4572            push_captured_symbol(
4573                &mut symbols,
4574                source,
4575                lang,
4576                name_node,
4577                def_node,
4578                kind,
4579                scope_chain,
4580                true,
4581            );
4582        }
4583        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
4584            push_captured_symbol(
4585                &mut symbols,
4586                source,
4587                lang,
4588                name_node,
4589                def_node,
4590                SymbolKind::Variable,
4591                vec![],
4592                true,
4593            );
4594        }
4595    }
4596
4597    dedup_symbols(&mut symbols);
4598    Ok(symbols)
4599}
4600
4601fn perl_package_name(source: &str, node: &Node) -> Option<String> {
4602    let mut cursor = node.walk();
4603    if !cursor.goto_first_child() {
4604        return None;
4605    }
4606
4607    loop {
4608        let child = cursor.node();
4609        if child.kind() == "package_name" {
4610            return Some(node_text(source, &child).to_string());
4611        }
4612        if !cursor.goto_next_sibling() {
4613            break;
4614        }
4615    }
4616
4617    None
4618}
4619
4620fn perl_scope_chain(node: &Node, source: &str) -> Vec<String> {
4621    let mut chain = Vec::new();
4622    let mut current = node.parent();
4623
4624    while let Some(parent) = current {
4625        if parent.kind() == "package_statement" {
4626            if let Some(name) = perl_package_name(source, &parent) {
4627                chain.push(name);
4628            }
4629        }
4630        current = parent.parent();
4631    }
4632
4633    if chain.is_empty() && node.kind() != "package_statement" {
4634        let mut sibling = node.prev_sibling();
4635        while let Some(prev) = sibling {
4636            if prev.kind() == "package_statement" {
4637                if let Some(name) = perl_package_name(source, &prev) {
4638                    chain.push(name);
4639                }
4640                break;
4641            }
4642            sibling = prev.prev_sibling();
4643        }
4644    }
4645
4646    chain.reverse();
4647    chain
4648}
4649
4650fn extract_perl_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
4651    let lang = LangId::Perl;
4652    let capture_names = query.capture_names();
4653    let mut symbols = Vec::new();
4654    let mut cursor = QueryCursor::new();
4655    let mut matches = cursor.matches(query, *root, source.as_bytes());
4656
4657    while let Some(m) = {
4658        matches.advance();
4659        matches.get()
4660    } {
4661        let mut package_name_node = None;
4662        let mut package_def_node = None;
4663        let mut fn_name_node = None;
4664        let mut fn_def_node = None;
4665        let mut var_name_node = None;
4666        let mut var_def_node = None;
4667
4668        for cap in m.captures {
4669            let Some(&name) = capture_names.get(cap.index as usize) else {
4670                continue;
4671            };
4672            match name {
4673                "package.name" => package_name_node = Some(cap.node),
4674                "package.def" => package_def_node = Some(cap.node),
4675                "fn.name" => fn_name_node = Some(cap.node),
4676                "fn.def" => fn_def_node = Some(cap.node),
4677                "var.name" => var_name_node = Some(cap.node),
4678                "var.def" => var_def_node = Some(cap.node),
4679                _ => {}
4680            }
4681        }
4682
4683        if let (Some(name_node), Some(def_node)) = (package_name_node, package_def_node) {
4684            push_captured_symbol(
4685                &mut symbols,
4686                source,
4687                lang,
4688                name_node,
4689                def_node,
4690                SymbolKind::Class,
4691                vec![],
4692                true,
4693            );
4694        }
4695        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
4696            let scope_chain = perl_scope_chain(&def_node, source);
4697            let kind = if scope_chain.is_empty() {
4698                SymbolKind::Function
4699            } else {
4700                SymbolKind::Method
4701            };
4702            push_captured_symbol(
4703                &mut symbols,
4704                source,
4705                lang,
4706                name_node,
4707                def_node,
4708                kind,
4709                scope_chain,
4710                true,
4711            );
4712        }
4713        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
4714            push_captured_symbol(
4715                &mut symbols,
4716                source,
4717                lang,
4718                name_node,
4719                def_node,
4720                SymbolKind::Variable,
4721                perl_scope_chain(&def_node, source),
4722                true,
4723            );
4724        }
4725    }
4726
4727    dedup_symbols(&mut symbols);
4728    Ok(symbols)
4729}
4730
4731fn extract_vue_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
4732    let mut symbols = Vec::new();
4733    collect_vue_sections(source, root, &mut symbols, true);
4734    dedup_symbols(&mut symbols);
4735    Ok(symbols)
4736}
4737
4738fn collect_vue_sections(
4739    source: &str,
4740    node: &Node,
4741    symbols: &mut Vec<Symbol>,
4742    allow_sections: bool,
4743) {
4744    let mut cursor = node.walk();
4745    if !cursor.goto_first_child() {
4746        return;
4747    }
4748
4749    loop {
4750        let child = cursor.node();
4751        if let Some(section_name) = vue_section_name(&child) {
4752            if allow_sections {
4753                symbols.push(Symbol {
4754                    name: section_name.to_string(),
4755                    kind: SymbolKind::Heading,
4756                    range: node_range(&child),
4757                    signature: vue_opening_tag_signature(source, &child),
4758                    scope_chain: vec![],
4759                    exported: false,
4760                    parent: None,
4761                });
4762            }
4763        } else {
4764            collect_vue_sections(
4765                source,
4766                &child,
4767                symbols,
4768                allow_sections && child.kind() == "document",
4769            );
4770        }
4771
4772        if !cursor.goto_next_sibling() {
4773            break;
4774        }
4775    }
4776}
4777
4778fn vue_section_name(node: &Node) -> Option<&'static str> {
4779    match node.kind() {
4780        "template_element" => Some("template"),
4781        "script_element" => Some("script"),
4782        "style_element" => Some("style"),
4783        _ => None,
4784    }
4785}
4786
4787fn vue_opening_tag_signature(source: &str, node: &Node) -> Option<String> {
4788    find_child_by_kind(*node, "start_tag")
4789        .or_else(|| find_child_by_kind(*node, "script_start_tag"))
4790        .or_else(|| find_child_by_kind(*node, "style_start_tag"))
4791        .or_else(|| find_child_by_kind(*node, "template_start_tag"))
4792        .map(|tag| node_text(source, &tag).trim().to_string())
4793}
4794
4795fn extract_html_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
4796    let mut headings: Vec<(u8, Symbol)> = Vec::new();
4797    collect_html_headings(source, root, &mut headings);
4798
4799    let total_lines = source.lines().count() as u32;
4800
4801    // Extend each heading's end_line to just before the next heading at the
4802    // same or shallower level (or EOF). This makes aft_zoom return the full
4803    // section content rather than just the heading element's single line.
4804    for i in 0..headings.len() {
4805        let level = headings[i].0;
4806        let section_end = headings[i + 1..]
4807            .iter()
4808            .find(|(l, _)| *l <= level)
4809            .map(|(_, s)| s.range.start_line.saturating_sub(1))
4810            .unwrap_or_else(|| total_lines.saturating_sub(1));
4811        headings[i].1.range.end_line = section_end;
4812        if section_end != headings[i].1.range.start_line {
4813            headings[i].1.range.end_col = 0;
4814        }
4815    }
4816
4817    // Build hierarchy: assign scope_chain and parent based on heading level
4818    let mut scope_stack: Vec<(u8, String)> = Vec::new(); // (level, name)
4819    for (level, symbol) in headings.iter_mut() {
4820        // Pop scope entries that are at the same level or deeper
4821        while scope_stack.last().is_some_and(|(l, _)| *l >= *level) {
4822            scope_stack.pop();
4823        }
4824        symbol.scope_chain = scope_stack.iter().map(|(_, name)| name.clone()).collect();
4825        symbol.parent = scope_stack.last().map(|(_, name)| name.clone());
4826        scope_stack.push((*level, symbol.name.clone()));
4827    }
4828
4829    Ok(headings.into_iter().map(|(_, s)| s).collect())
4830}
4831
4832/// Recursively collect h1-h6 elements from the HTML tree.
4833fn collect_html_headings(source: &str, node: &Node, headings: &mut Vec<(u8, Symbol)>) {
4834    let mut cursor = node.walk();
4835    if !cursor.goto_first_child() {
4836        return;
4837    }
4838
4839    loop {
4840        let child = cursor.node();
4841        if child.kind() == "element" {
4842            // Check if this element's start tag is h1-h6
4843            if let Some(start_tag) = child
4844                .child_by_field_name("start_tag")
4845                .or_else(|| child.child(0).filter(|c| c.kind() == "start_tag"))
4846            {
4847                if let Some(tag_name_node) = start_tag
4848                    .child_by_field_name("tag_name")
4849                    .or_else(|| start_tag.child(1).filter(|c| c.kind() == "tag_name"))
4850                {
4851                    let tag_name = node_text(source, &tag_name_node).to_lowercase();
4852                    if let Some(level) = match tag_name.as_str() {
4853                        "h1" => Some(1u8),
4854                        "h2" => Some(2),
4855                        "h3" => Some(3),
4856                        "h4" => Some(4),
4857                        "h5" => Some(5),
4858                        "h6" => Some(6),
4859                        _ => None,
4860                    } {
4861                        // Extract text content from the element
4862                        let text = extract_element_text(source, &child).trim().to_string();
4863                        if !text.is_empty() {
4864                            let range = node_range(&child);
4865                            let signature = format!("<h{}> {}", level, text);
4866                            headings.push((
4867                                level,
4868                                Symbol {
4869                                    name: text,
4870                                    kind: SymbolKind::Heading,
4871                                    range,
4872                                    signature: Some(signature),
4873                                    scope_chain: vec![], // filled later
4874                                    exported: false,
4875                                    parent: None, // filled later
4876                                },
4877                            ));
4878                        }
4879                    }
4880                }
4881            }
4882            // Recurse into element children (nested headings)
4883            collect_html_headings(source, &child, headings);
4884        } else {
4885            // Recurse into other node types (document, body, etc.)
4886            collect_html_headings(source, &child, headings);
4887        }
4888
4889        if !cursor.goto_next_sibling() {
4890            break;
4891        }
4892    }
4893}
4894
4895/// Extract text content from an HTML element, stripping tags.
4896fn extract_element_text(source: &str, node: &Node) -> String {
4897    let mut text = String::new();
4898    let mut cursor = node.walk();
4899    if !cursor.goto_first_child() {
4900        return text;
4901    }
4902    loop {
4903        let child = cursor.node();
4904        match child.kind() {
4905            "text" => {
4906                text.push_str(node_text(source, &child));
4907            }
4908            "element" => {
4909                // Recurse into nested elements (e.g., <strong>, <em>, <a>)
4910                text.push_str(&extract_element_text(source, &child));
4911            }
4912            _ => {}
4913        }
4914        if !cursor.goto_next_sibling() {
4915            break;
4916        }
4917    }
4918    text
4919}
4920
4921/// Extract markdown headings as symbols.
4922/// Each heading becomes a symbol with kind `Heading`, and its range covers the entire
4923/// section (from the heading to the next heading at the same or higher level, or EOF).
4924fn extract_md_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
4925    let mut symbols = Vec::new();
4926    extract_md_sections(source, root, &mut symbols, &[]);
4927    Ok(symbols)
4928}
4929
4930/// Recursively walk `section` nodes to build the heading hierarchy.
4931fn extract_md_sections(
4932    source: &str,
4933    node: &Node,
4934    symbols: &mut Vec<Symbol>,
4935    scope_chain: &[String],
4936) {
4937    let mut cursor = node.walk();
4938    if !cursor.goto_first_child() {
4939        return;
4940    }
4941
4942    loop {
4943        let child = cursor.node();
4944        match child.kind() {
4945            "section" => {
4946                // A section contains an atx_heading as its first child,
4947                // followed by content and possibly nested sections.
4948                let mut section_cursor = child.walk();
4949                let mut heading_name = String::new();
4950                let mut heading_level: u8 = 0;
4951
4952                if section_cursor.goto_first_child() {
4953                    loop {
4954                        let section_child = section_cursor.node();
4955                        if section_child.kind() == "atx_heading" {
4956                            // Extract heading level from marker type
4957                            let mut h_cursor = section_child.walk();
4958                            if h_cursor.goto_first_child() {
4959                                loop {
4960                                    let h_child = h_cursor.node();
4961                                    let kind = h_child.kind();
4962                                    if kind.starts_with("atx_h") && kind.ends_with("_marker") {
4963                                        // "atx_h1_marker" → level 1, "atx_h2_marker" → level 2, etc.
4964                                        heading_level = kind
4965                                            .strip_prefix("atx_h")
4966                                            .and_then(|s| s.strip_suffix("_marker"))
4967                                            .and_then(|s| s.parse::<u8>().ok())
4968                                            .unwrap_or(1);
4969                                    } else if h_child.kind() == "inline" {
4970                                        heading_name =
4971                                            node_text(source, &h_child).trim().to_string();
4972                                    }
4973                                    if !h_cursor.goto_next_sibling() {
4974                                        break;
4975                                    }
4976                                }
4977                            }
4978                        }
4979                        if !section_cursor.goto_next_sibling() {
4980                            break;
4981                        }
4982                    }
4983                }
4984
4985                if !heading_name.is_empty() {
4986                    let range = node_range(&child);
4987                    let signature = format!(
4988                        "{} {}",
4989                        "#".repeat((heading_level as usize).min(6)),
4990                        heading_name
4991                    );
4992
4993                    symbols.push(Symbol {
4994                        name: heading_name.clone(),
4995                        kind: SymbolKind::Heading,
4996                        range,
4997                        signature: Some(signature),
4998                        scope_chain: scope_chain.to_vec(),
4999                        exported: false,
5000                        parent: scope_chain.last().cloned(),
5001                    });
5002
5003                    // Recurse into the section for nested headings
5004                    let mut new_scope = scope_chain.to_vec();
5005                    new_scope.push(heading_name);
5006                    extract_md_sections(source, &child, symbols, &new_scope);
5007                }
5008            }
5009            _ => {}
5010        }
5011
5012        if !cursor.goto_next_sibling() {
5013            break;
5014        }
5015    }
5016}
5017
5018/// Remove duplicate symbols based on (name, kind, start_line).
5019/// Class declarations can match both "class" and "method" patterns,
5020/// producing duplicates.
5021fn dedup_symbols(symbols: &mut Vec<Symbol>) {
5022    let mut seen = std::collections::HashSet::new();
5023    symbols.retain(|s| {
5024        let key = (s.name.clone(), format!("{:?}", s.kind), s.range.start_line);
5025        seen.insert(key)
5026    });
5027}
5028
5029/// Provider that uses tree-sitter for real symbol extraction.
5030/// Implements the `LanguageProvider` trait from `language.rs`.
5031pub struct TreeSitterProvider {
5032    parser: RefCell<FileParser>,
5033}
5034
5035#[derive(Debug, Clone)]
5036struct ReExportTarget {
5037    file: PathBuf,
5038    symbol_name: String,
5039}
5040
5041impl TreeSitterProvider {
5042    /// Create a new `TreeSitterProvider` backed by a fresh `FileParser`.
5043    pub fn new() -> Self {
5044        Self::with_symbol_cache(Arc::new(RwLock::new(SymbolCache::new())))
5045    }
5046
5047    /// Create a new `TreeSitterProvider` backed by a shared symbol cache.
5048    pub fn with_symbol_cache(symbol_cache: SharedSymbolCache) -> Self {
5049        Self {
5050            parser: RefCell::new(FileParser::with_symbol_cache(symbol_cache)),
5051        }
5052    }
5053
5054    /// Return shared symbol cache entries for status reporting.
5055    pub fn symbol_cache_len(&self) -> usize {
5056        let parser = self.parser.borrow();
5057        parser.symbol_cache_len()
5058    }
5059
5060    /// Shared symbol cache backing this provider.
5061    pub fn symbol_cache(&self) -> SharedSymbolCache {
5062        let parser = self.parser.borrow();
5063        parser.symbol_cache()
5064    }
5065
5066    fn resolve_symbol_inner(
5067        &self,
5068        file: &Path,
5069        name: &str,
5070        depth: usize,
5071        visited: &mut HashSet<(PathBuf, String)>,
5072    ) -> Result<Vec<SymbolMatch>, AftError> {
5073        if depth > MAX_REEXPORT_DEPTH {
5074            return Ok(Vec::new());
5075        }
5076
5077        let canonical_file = std::fs::canonicalize(file).unwrap_or_else(|_| file.to_path_buf());
5078        if !visited.insert((canonical_file, name.to_string())) {
5079            return Ok(Vec::new());
5080        }
5081
5082        let symbols = self.parser.borrow_mut().extract_symbols(file)?;
5083        let local_matches = symbol_matches_in_file(file, &symbols, name);
5084        if !local_matches.is_empty() {
5085            return Ok(local_matches);
5086        }
5087
5088        if name == "default" {
5089            let default_matches = self.resolve_local_default_export(file, &symbols)?;
5090            if !default_matches.is_empty() {
5091                return Ok(default_matches);
5092            }
5093        }
5094
5095        let reexport_targets = self.collect_reexport_targets(file, name)?;
5096        let mut matches = Vec::new();
5097        let mut seen = HashSet::new();
5098        for target in reexport_targets {
5099            for resolved in
5100                self.resolve_symbol_inner(&target.file, &target.symbol_name, depth + 1, visited)?
5101            {
5102                let key = format!(
5103                    "{}:{}:{}:{}:{}:{}",
5104                    resolved.file,
5105                    resolved.symbol.name,
5106                    resolved.symbol.range.start_line,
5107                    resolved.symbol.range.start_col,
5108                    resolved.symbol.range.end_line,
5109                    resolved.symbol.range.end_col
5110                );
5111                if seen.insert(key) {
5112                    matches.push(resolved);
5113                }
5114            }
5115        }
5116
5117        Ok(matches)
5118    }
5119
5120    fn collect_reexport_targets(
5121        &self,
5122        file: &Path,
5123        requested_name: &str,
5124    ) -> Result<Vec<ReExportTarget>, AftError> {
5125        let (source, tree, lang) = self.read_parsed_file(file)?;
5126        if !matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
5127            return Ok(Vec::new());
5128        }
5129
5130        let mut targets = Vec::new();
5131        let root = tree.root_node();
5132        let from_dir = file.parent().unwrap_or_else(|| Path::new("."));
5133
5134        let mut cursor = root.walk();
5135        if !cursor.goto_first_child() {
5136            return Ok(targets);
5137        }
5138
5139        loop {
5140            let node = cursor.node();
5141            if node.kind() == "export_statement" {
5142                let Some(source_node) = node.child_by_field_name("source") else {
5143                    if let Some(export_clause) = find_child_by_kind(node, "export_clause") {
5144                        if let Some(symbol_name) =
5145                            resolve_export_clause_name(&source, &export_clause, requested_name)
5146                        {
5147                            targets.push(ReExportTarget {
5148                                file: file.to_path_buf(),
5149                                symbol_name,
5150                            });
5151                        }
5152                    }
5153                    if !cursor.goto_next_sibling() {
5154                        break;
5155                    }
5156                    continue;
5157                };
5158
5159                let Some(module_path) = string_content(&source, &source_node) else {
5160                    if !cursor.goto_next_sibling() {
5161                        break;
5162                    }
5163                    continue;
5164                };
5165
5166                let Some(target_file) = resolve_module_path(from_dir, &module_path) else {
5167                    if !cursor.goto_next_sibling() {
5168                        break;
5169                    }
5170                    continue;
5171                };
5172
5173                if let Some(export_clause) = find_child_by_kind(node, "export_clause") {
5174                    if let Some(symbol_name) =
5175                        resolve_export_clause_name(&source, &export_clause, requested_name)
5176                    {
5177                        targets.push(ReExportTarget {
5178                            file: target_file,
5179                            symbol_name,
5180                        });
5181                    }
5182                } else if export_statement_has_wildcard(&source, &node) {
5183                    targets.push(ReExportTarget {
5184                        file: target_file,
5185                        symbol_name: requested_name.to_string(),
5186                    });
5187                }
5188            }
5189
5190            if !cursor.goto_next_sibling() {
5191                break;
5192            }
5193        }
5194
5195        Ok(targets)
5196    }
5197
5198    fn resolve_local_default_export(
5199        &self,
5200        file: &Path,
5201        symbols: &[Symbol],
5202    ) -> Result<Vec<SymbolMatch>, AftError> {
5203        let (source, tree, lang) = self.read_parsed_file(file)?;
5204        if !matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
5205            return Ok(Vec::new());
5206        }
5207
5208        let root = tree.root_node();
5209        let mut matches = Vec::new();
5210        let mut seen = HashSet::new();
5211
5212        let mut cursor = root.walk();
5213        if !cursor.goto_first_child() {
5214            return Ok(matches);
5215        }
5216
5217        loop {
5218            let node = cursor.node();
5219            if node.kind() == "export_statement"
5220                && node.child_by_field_name("source").is_none()
5221                && node_contains_token(&source, &node, "default")
5222            {
5223                if let Some(target_name) = default_export_target_name(&source, &node) {
5224                    for symbol_match in symbol_matches_in_file(file, symbols, &target_name) {
5225                        let key = format!(
5226                            "{}:{}:{}:{}:{}:{}",
5227                            symbol_match.file,
5228                            symbol_match.symbol.name,
5229                            symbol_match.symbol.range.start_line,
5230                            symbol_match.symbol.range.start_col,
5231                            symbol_match.symbol.range.end_line,
5232                            symbol_match.symbol.range.end_col
5233                        );
5234                        if seen.insert(key) {
5235                            matches.push(symbol_match);
5236                        }
5237                    }
5238                }
5239            }
5240
5241            if !cursor.goto_next_sibling() {
5242                break;
5243            }
5244        }
5245
5246        Ok(matches)
5247    }
5248
5249    fn read_parsed_file(&self, file: &Path) -> Result<(String, Tree, LangId), AftError> {
5250        let source = std::fs::read_to_string(file).map_err(|e| AftError::FileNotFound {
5251            path: format!("{}: {}", file.display(), e),
5252        })?;
5253        let (tree, lang) = {
5254            let mut parser = self.parser.borrow_mut();
5255            parser.parse_cloned(file)?
5256        };
5257        Ok((source, tree, lang))
5258    }
5259}
5260
5261fn symbol_matches_in_file(file: &Path, symbols: &[Symbol], name: &str) -> Vec<SymbolMatch> {
5262    symbols
5263        .iter()
5264        .filter(|symbol| symbol.name == name)
5265        .cloned()
5266        .map(|symbol| SymbolMatch {
5267            file: file.display().to_string(),
5268            symbol,
5269        })
5270        .collect()
5271}
5272
5273fn string_content(source: &str, node: &Node) -> Option<String> {
5274    let text = node_text(source, node);
5275    if text.len() < 2 {
5276        return None;
5277    }
5278
5279    Some(
5280        text.trim_start_matches(|c| c == '\'' || c == '"')
5281            .trim_end_matches(|c| c == '\'' || c == '"')
5282            .to_string(),
5283    )
5284}
5285
5286fn find_child_by_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
5287    let mut cursor = node.walk();
5288    if !cursor.goto_first_child() {
5289        return None;
5290    }
5291
5292    loop {
5293        let child = cursor.node();
5294        if child.kind() == kind {
5295            return Some(child);
5296        }
5297        if !cursor.goto_next_sibling() {
5298            break;
5299        }
5300    }
5301
5302    None
5303}
5304
5305fn resolve_export_clause_name(
5306    source: &str,
5307    export_clause: &Node,
5308    requested_name: &str,
5309) -> Option<String> {
5310    let mut cursor = export_clause.walk();
5311    if !cursor.goto_first_child() {
5312        return None;
5313    }
5314
5315    loop {
5316        let child = cursor.node();
5317        if child.kind() == "export_specifier" {
5318            let (source_name, exported_name) = export_specifier_names(source, &child)?;
5319            if exported_name == requested_name {
5320                return Some(source_name);
5321            }
5322        }
5323
5324        if !cursor.goto_next_sibling() {
5325            break;
5326        }
5327    }
5328
5329    None
5330}
5331
5332fn export_specifier_names(source: &str, specifier: &Node) -> Option<(String, String)> {
5333    let source_name = specifier
5334        .child_by_field_name("name")
5335        .map(|node| node_text(source, &node).to_string());
5336    let alias_name = specifier
5337        .child_by_field_name("alias")
5338        .map(|node| node_text(source, &node).to_string());
5339
5340    if let Some(source_name) = source_name {
5341        let exported_name = alias_name.unwrap_or_else(|| source_name.clone());
5342        return Some((source_name, exported_name));
5343    }
5344
5345    let mut names = Vec::new();
5346    let mut cursor = specifier.walk();
5347    if cursor.goto_first_child() {
5348        loop {
5349            let child = cursor.node();
5350            let child_text = node_text(source, &child).trim();
5351            if matches!(
5352                child.kind(),
5353                "identifier" | "type_identifier" | "property_identifier"
5354            ) || child_text == "default"
5355            {
5356                names.push(child_text.to_string());
5357            }
5358            if !cursor.goto_next_sibling() {
5359                break;
5360            }
5361        }
5362    }
5363
5364    match names.as_slice() {
5365        [name] => Some((name.clone(), name.clone())),
5366        [source_name, exported_name, ..] => Some((source_name.clone(), exported_name.clone())),
5367        _ => None,
5368    }
5369}
5370
5371fn export_statement_has_wildcard(source: &str, node: &Node) -> bool {
5372    let mut cursor = node.walk();
5373    if !cursor.goto_first_child() {
5374        return false;
5375    }
5376
5377    loop {
5378        if node_text(source, &cursor.node()).trim() == "*" {
5379            return true;
5380        }
5381        if !cursor.goto_next_sibling() {
5382            break;
5383        }
5384    }
5385
5386    false
5387}
5388
5389fn node_contains_token(source: &str, node: &Node, token: &str) -> bool {
5390    let mut cursor = node.walk();
5391    if !cursor.goto_first_child() {
5392        return false;
5393    }
5394
5395    loop {
5396        if node_text(source, &cursor.node()).trim() == token {
5397            return true;
5398        }
5399        if !cursor.goto_next_sibling() {
5400            break;
5401        }
5402    }
5403
5404    false
5405}
5406
5407fn default_export_target_name(source: &str, export_stmt: &Node) -> Option<String> {
5408    let mut cursor = export_stmt.walk();
5409    if !cursor.goto_first_child() {
5410        return None;
5411    }
5412
5413    loop {
5414        let child = cursor.node();
5415        match child.kind() {
5416            "function_declaration"
5417            | "class_declaration"
5418            | "interface_declaration"
5419            | "enum_declaration"
5420            | "type_alias_declaration"
5421            | "lexical_declaration" => {
5422                if let Some(name_node) = child.child_by_field_name("name") {
5423                    return Some(node_text(source, &name_node).to_string());
5424                }
5425
5426                if child.kind() == "lexical_declaration" {
5427                    let mut child_cursor = child.walk();
5428                    if child_cursor.goto_first_child() {
5429                        loop {
5430                            let nested = child_cursor.node();
5431                            if nested.kind() == "variable_declarator" {
5432                                if let Some(name_node) = nested.child_by_field_name("name") {
5433                                    return Some(node_text(source, &name_node).to_string());
5434                                }
5435                            }
5436                            if !child_cursor.goto_next_sibling() {
5437                                break;
5438                            }
5439                        }
5440                    }
5441                }
5442            }
5443            "identifier" | "type_identifier" => {
5444                let text = node_text(source, &child);
5445                if text != "export" && text != "default" {
5446                    return Some(text.to_string());
5447                }
5448            }
5449            _ => {}
5450        }
5451
5452        if !cursor.goto_next_sibling() {
5453            break;
5454        }
5455    }
5456
5457    None
5458}
5459
5460impl crate::language::LanguageProvider for TreeSitterProvider {
5461    fn resolve_symbol(&self, file: &Path, name: &str) -> Result<Vec<SymbolMatch>, AftError> {
5462        let matches = self.resolve_symbol_inner(file, name, 0, &mut HashSet::new())?;
5463
5464        if matches.is_empty() {
5465            Err(AftError::SymbolNotFound {
5466                name: name.to_string(),
5467                file: file.display().to_string(),
5468            })
5469        } else {
5470            Ok(matches)
5471        }
5472    }
5473
5474    fn list_symbols(&self, file: &Path) -> Result<Vec<Symbol>, AftError> {
5475        self.parser.borrow_mut().extract_symbols(file)
5476    }
5477
5478    fn as_any(&self) -> &dyn std::any::Any {
5479        self
5480    }
5481}
5482
5483#[cfg(test)]
5484mod tests {
5485    use super::*;
5486    use crate::language::LanguageProvider;
5487    use crate::symbol_cache_disk;
5488    use std::path::PathBuf;
5489
5490    fn fixture_path(name: &str) -> PathBuf {
5491        PathBuf::from(env!("CARGO_MANIFEST_DIR"))
5492            .join("tests")
5493            .join("fixtures")
5494            .join(name)
5495    }
5496
5497    fn test_symbol(name: &str) -> Symbol {
5498        Symbol {
5499            name: name.to_string(),
5500            kind: SymbolKind::Function,
5501            range: Range {
5502                start_line: 0,
5503                start_col: 0,
5504                end_line: 0,
5505                end_col: 10,
5506            },
5507            signature: Some(format!("fn {name}()")),
5508            scope_chain: Vec::new(),
5509            exported: true,
5510            parent: None,
5511        }
5512    }
5513
5514    #[test]
5515    fn symbol_cache_load_from_disk_round_trips_synthetic_entry() {
5516        let project = tempfile::tempdir().expect("create project dir");
5517        let storage = tempfile::tempdir().expect("create storage dir");
5518        let source = project.path().join("src/lib.rs");
5519        std::fs::create_dir_all(source.parent().expect("source parent"))
5520            .expect("create source dir");
5521        std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
5522        let mtime = std::fs::metadata(&source)
5523            .expect("stat source")
5524            .modified()
5525            .expect("source mtime");
5526        let content = std::fs::read(&source).expect("read source");
5527        let size = content.len() as u64;
5528        let hash = crate::cache_freshness::hash_bytes(&content);
5529
5530        let mut cache = SymbolCache::new();
5531        cache.set_project_root(project.path().to_path_buf());
5532        cache.insert(
5533            source.clone(),
5534            mtime,
5535            size,
5536            hash,
5537            vec![test_symbol("cached")],
5538        );
5539        symbol_cache_disk::write_to_disk(&cache, storage.path(), "unit-project")
5540            .expect("write symbol cache");
5541
5542        let mut restored = SymbolCache::new();
5543        let loaded = restored.load_from_disk(storage.path(), "unit-project", project.path());
5544        let symbols = restored.get(&source, mtime).expect("restored symbols");
5545
5546        assert_eq!(loaded, 1);
5547        assert_eq!(symbols.len(), 1);
5548        assert_eq!(symbols[0].name, "cached");
5549    }
5550
5551    #[test]
5552    fn symbol_cache_load_from_disk_drops_stale_synthetic_entry() {
5553        let project = tempfile::tempdir().expect("create project dir");
5554        let storage = tempfile::tempdir().expect("create storage dir");
5555        let source = project.path().join("src/lib.rs");
5556        std::fs::create_dir_all(source.parent().expect("source parent"))
5557            .expect("create source dir");
5558        std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
5559        let mtime = std::fs::metadata(&source)
5560            .expect("stat source")
5561            .modified()
5562            .expect("source mtime");
5563        let content = std::fs::read(&source).expect("read source");
5564        let size = content.len() as u64;
5565        let hash = crate::cache_freshness::hash_bytes(&content);
5566
5567        let mut cache = SymbolCache::new();
5568        cache.set_project_root(project.path().to_path_buf());
5569        cache.insert(
5570            source.clone(),
5571            mtime,
5572            size,
5573            hash,
5574            vec![test_symbol("cached")],
5575        );
5576        symbol_cache_disk::write_to_disk(&cache, storage.path(), "stale-unit-project")
5577            .expect("write symbol cache");
5578
5579        std::fs::write(&source, "pub fn cached() {}\npub fn fresh() {}\n").expect("change source");
5580
5581        let mut restored = SymbolCache::new();
5582        let loaded = restored.load_from_disk(storage.path(), "stale-unit-project", project.path());
5583
5584        assert_eq!(loaded, 0);
5585        assert_eq!(restored.len(), 0);
5586    }
5587
5588    #[test]
5589    fn stale_prewarm_generation_cannot_repopulate_symbol_cache_after_reset() {
5590        let project = tempfile::tempdir().expect("create project dir");
5591        let storage = tempfile::tempdir().expect("create storage dir");
5592        let source = project.path().join("src/lib.rs");
5593        std::fs::create_dir_all(source.parent().expect("source parent"))
5594            .expect("create source dir");
5595        std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
5596        let mtime = std::fs::metadata(&source)
5597            .expect("stat source")
5598            .modified()
5599            .expect("source mtime");
5600        let content = std::fs::read(&source).expect("read source");
5601        let size = content.len() as u64;
5602        let hash = crate::cache_freshness::hash_bytes(&content);
5603
5604        let mut disk_cache = SymbolCache::new();
5605        disk_cache.set_project_root(project.path().to_path_buf());
5606        disk_cache.insert(
5607            source.clone(),
5608            mtime,
5609            size,
5610            hash,
5611            vec![test_symbol("cached")],
5612        );
5613        symbol_cache_disk::write_to_disk(&disk_cache, storage.path(), "prewarm-reset")
5614            .expect("write symbol cache");
5615
5616        let shared = Arc::new(RwLock::new(SymbolCache::new()));
5617        let stale_generation = shared.write().unwrap().reset();
5618        let active_generation = shared.write().unwrap().reset();
5619        assert_ne!(stale_generation, active_generation);
5620
5621        {
5622            let mut cache = shared.write().unwrap();
5623            assert!(!cache
5624                .set_project_root_for_generation(stale_generation, project.path().to_path_buf()));
5625            assert_eq!(
5626                cache.load_from_disk_for_generation(
5627                    stale_generation,
5628                    storage.path(),
5629                    "prewarm-reset",
5630                    project.path()
5631                ),
5632                0
5633            );
5634        }
5635
5636        let mut stale_parser =
5637            FileParser::with_symbol_cache_generation(Arc::clone(&shared), Some(stale_generation));
5638        stale_parser
5639            .extract_symbols(&source)
5640            .expect("stale prewarm parses source but must not write cache");
5641
5642        let cache = shared.read().unwrap();
5643        assert_eq!(cache.generation(), active_generation);
5644        assert_eq!(cache.len(), 0);
5645        assert!(cache.project_root().is_none());
5646        assert!(!cache.contains_key(&source));
5647    }
5648
5649    // --- Language detection ---
5650
5651    #[test]
5652    fn detect_ts() {
5653        assert_eq!(
5654            detect_language(Path::new("foo.ts")),
5655            Some(LangId::TypeScript)
5656        );
5657    }
5658
5659    #[test]
5660    fn detect_tsx() {
5661        assert_eq!(detect_language(Path::new("foo.tsx")), Some(LangId::Tsx));
5662    }
5663
5664    #[test]
5665    fn detect_js() {
5666        assert_eq!(
5667            detect_language(Path::new("foo.js")),
5668            Some(LangId::JavaScript)
5669        );
5670    }
5671
5672    #[test]
5673    fn detect_jsx() {
5674        assert_eq!(
5675            detect_language(Path::new("foo.jsx")),
5676            Some(LangId::JavaScript)
5677        );
5678    }
5679
5680    #[test]
5681    fn detect_py() {
5682        assert_eq!(detect_language(Path::new("foo.py")), Some(LangId::Python));
5683    }
5684
5685    #[test]
5686    fn detect_rs() {
5687        assert_eq!(detect_language(Path::new("foo.rs")), Some(LangId::Rust));
5688    }
5689
5690    #[test]
5691    fn detect_go() {
5692        assert_eq!(detect_language(Path::new("foo.go")), Some(LangId::Go));
5693    }
5694
5695    #[test]
5696    fn detect_c() {
5697        assert_eq!(detect_language(Path::new("foo.c")), Some(LangId::C));
5698    }
5699
5700    #[test]
5701    fn detect_h() {
5702        assert_eq!(detect_language(Path::new("foo.h")), Some(LangId::C));
5703    }
5704
5705    #[test]
5706    fn detect_cc() {
5707        assert_eq!(detect_language(Path::new("foo.cc")), Some(LangId::Cpp));
5708    }
5709
5710    #[test]
5711    fn detect_cpp() {
5712        assert_eq!(detect_language(Path::new("foo.cpp")), Some(LangId::Cpp));
5713    }
5714
5715    #[test]
5716    fn detect_cxx() {
5717        assert_eq!(detect_language(Path::new("foo.cxx")), Some(LangId::Cpp));
5718    }
5719
5720    #[test]
5721    fn detect_hpp() {
5722        assert_eq!(detect_language(Path::new("foo.hpp")), Some(LangId::Cpp));
5723    }
5724
5725    #[test]
5726    fn detect_hh() {
5727        assert_eq!(detect_language(Path::new("foo.hh")), Some(LangId::Cpp));
5728    }
5729
5730    #[test]
5731    fn detect_zig() {
5732        assert_eq!(detect_language(Path::new("foo.zig")), Some(LangId::Zig));
5733    }
5734
5735    #[test]
5736    fn detect_cs() {
5737        assert_eq!(detect_language(Path::new("foo.cs")), Some(LangId::CSharp));
5738    }
5739
5740    #[test]
5741    fn detect_unknown_returns_none() {
5742        assert_eq!(detect_language(Path::new("foo.txt")), None);
5743    }
5744
5745    // --- Unsupported extension error ---
5746
5747    #[test]
5748    fn unsupported_extension_returns_invalid_request() {
5749        // Use a file that exists but has an unsupported extension
5750        let path = fixture_path("sample.ts");
5751        let bad_path = path.with_extension("txt");
5752        // Create a dummy file so the error comes from language detection, not I/O
5753        std::fs::write(&bad_path, "hello").unwrap();
5754        let provider = TreeSitterProvider::new();
5755        let result = provider.list_symbols(&bad_path);
5756        std::fs::remove_file(&bad_path).ok();
5757        match result {
5758            Err(AftError::InvalidRequest { message }) => {
5759                assert!(
5760                    message.contains("unsupported file extension"),
5761                    "msg: {}",
5762                    message
5763                );
5764                assert!(message.contains("txt"), "msg: {}", message);
5765            }
5766            other => panic!("expected InvalidRequest, got {:?}", other),
5767        }
5768    }
5769
5770    // --- TypeScript extraction ---
5771
5772    #[test]
5773    fn ts_extracts_all_symbol_kinds() {
5774        let provider = TreeSitterProvider::new();
5775        let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
5776
5777        let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
5778        assert!(
5779            names.contains(&"greet"),
5780            "missing function greet: {:?}",
5781            names
5782        );
5783        assert!(names.contains(&"add"), "missing arrow fn add: {:?}", names);
5784        assert!(
5785            names.contains(&"UserService"),
5786            "missing class UserService: {:?}",
5787            names
5788        );
5789        assert!(
5790            names.contains(&"Config"),
5791            "missing interface Config: {:?}",
5792            names
5793        );
5794        assert!(
5795            names.contains(&"Status"),
5796            "missing enum Status: {:?}",
5797            names
5798        );
5799        assert!(
5800            names.contains(&"UserId"),
5801            "missing type alias UserId: {:?}",
5802            names
5803        );
5804        assert!(
5805            names.contains(&"internalHelper"),
5806            "missing non-exported fn: {:?}",
5807            names
5808        );
5809
5810        // At least 6 unique symbols as required
5811        assert!(
5812            symbols.len() >= 6,
5813            "expected ≥6 symbols, got {}: {:?}",
5814            symbols.len(),
5815            names
5816        );
5817    }
5818
5819    #[test]
5820    fn ts_symbol_kinds_correct() {
5821        let provider = TreeSitterProvider::new();
5822        let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
5823
5824        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
5825
5826        assert_eq!(find("greet").kind, SymbolKind::Function);
5827        assert_eq!(find("add").kind, SymbolKind::Function); // arrow fn → Function
5828        assert_eq!(find("UserService").kind, SymbolKind::Class);
5829        assert_eq!(find("Config").kind, SymbolKind::Interface);
5830        assert_eq!(find("Status").kind, SymbolKind::Enum);
5831        assert_eq!(find("UserId").kind, SymbolKind::TypeAlias);
5832    }
5833
5834    #[test]
5835    fn ts_export_detection() {
5836        let provider = TreeSitterProvider::new();
5837        let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
5838
5839        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
5840
5841        assert!(find("greet").exported, "greet should be exported");
5842        assert!(find("add").exported, "add should be exported");
5843        assert!(
5844            find("UserService").exported,
5845            "UserService should be exported"
5846        );
5847        assert!(find("Config").exported, "Config should be exported");
5848        assert!(find("Status").exported, "Status should be exported");
5849        assert!(
5850            !find("internalHelper").exported,
5851            "internalHelper should not be exported"
5852        );
5853    }
5854
5855    #[test]
5856    fn ts_method_scope_chain() {
5857        let provider = TreeSitterProvider::new();
5858        let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
5859
5860        let methods: Vec<&Symbol> = symbols
5861            .iter()
5862            .filter(|s| s.kind == SymbolKind::Method)
5863            .collect();
5864        assert!(!methods.is_empty(), "should have at least one method");
5865
5866        for method in &methods {
5867            assert_eq!(
5868                method.scope_chain,
5869                vec!["UserService"],
5870                "method {} should have UserService in scope chain",
5871                method.name
5872            );
5873            assert_eq!(method.parent.as_deref(), Some("UserService"));
5874        }
5875    }
5876
5877    #[test]
5878    fn ts_signatures_present() {
5879        let provider = TreeSitterProvider::new();
5880        let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
5881
5882        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
5883
5884        let greet_sig = find("greet").signature.as_ref().unwrap();
5885        assert!(
5886            greet_sig.contains("greet"),
5887            "signature should contain function name: {}",
5888            greet_sig
5889        );
5890    }
5891
5892    #[test]
5893    fn ts_ranges_valid() {
5894        let provider = TreeSitterProvider::new();
5895        let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
5896
5897        for s in &symbols {
5898            assert!(
5899                s.range.end_line >= s.range.start_line,
5900                "symbol {} has invalid range: {:?}",
5901                s.name,
5902                s.range
5903            );
5904        }
5905    }
5906
5907    // --- JavaScript extraction ---
5908
5909    #[test]
5910    fn js_extracts_core_symbols() {
5911        let provider = TreeSitterProvider::new();
5912        let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
5913
5914        let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
5915        assert!(
5916            names.contains(&"multiply"),
5917            "missing function multiply: {:?}",
5918            names
5919        );
5920        assert!(
5921            names.contains(&"divide"),
5922            "missing arrow fn divide: {:?}",
5923            names
5924        );
5925        assert!(
5926            names.contains(&"EventEmitter"),
5927            "missing class EventEmitter: {:?}",
5928            names
5929        );
5930        assert!(
5931            names.contains(&"main"),
5932            "missing default export fn main: {:?}",
5933            names
5934        );
5935
5936        assert!(
5937            symbols.len() >= 4,
5938            "expected ≥4 symbols, got {}: {:?}",
5939            symbols.len(),
5940            names
5941        );
5942    }
5943
5944    #[test]
5945    fn js_arrow_fn_correctly_named() {
5946        let provider = TreeSitterProvider::new();
5947        let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
5948
5949        let divide = symbols.iter().find(|s| s.name == "divide").unwrap();
5950        assert_eq!(divide.kind, SymbolKind::Function);
5951        assert!(divide.exported, "divide should be exported");
5952
5953        let internal = symbols.iter().find(|s| s.name == "internalUtil").unwrap();
5954        assert_eq!(internal.kind, SymbolKind::Function);
5955        assert!(!internal.exported, "internalUtil should not be exported");
5956    }
5957
5958    #[test]
5959    fn js_method_scope_chain() {
5960        let provider = TreeSitterProvider::new();
5961        let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
5962
5963        let methods: Vec<&Symbol> = symbols
5964            .iter()
5965            .filter(|s| s.kind == SymbolKind::Method)
5966            .collect();
5967
5968        for method in &methods {
5969            assert_eq!(
5970                method.scope_chain,
5971                vec!["EventEmitter"],
5972                "method {} should have EventEmitter in scope chain",
5973                method.name
5974            );
5975        }
5976    }
5977
5978    // --- TSX extraction ---
5979
5980    #[test]
5981    fn tsx_extracts_react_component() {
5982        let provider = TreeSitterProvider::new();
5983        let symbols = provider.list_symbols(&fixture_path("sample.tsx")).unwrap();
5984
5985        let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
5986        assert!(
5987            names.contains(&"Button"),
5988            "missing React component Button: {:?}",
5989            names
5990        );
5991        assert!(
5992            names.contains(&"Counter"),
5993            "missing class Counter: {:?}",
5994            names
5995        );
5996        assert!(
5997            names.contains(&"formatLabel"),
5998            "missing function formatLabel: {:?}",
5999            names
6000        );
6001
6002        assert!(
6003            symbols.len() >= 2,
6004            "expected ≥2 symbols, got {}: {:?}",
6005            symbols.len(),
6006            names
6007        );
6008    }
6009
6010    #[test]
6011    fn tsx_jsx_doesnt_break_parser() {
6012        // Main assertion: TSX grammar handles JSX without errors
6013        let provider = TreeSitterProvider::new();
6014        let result = provider.list_symbols(&fixture_path("sample.tsx"));
6015        assert!(
6016            result.is_ok(),
6017            "TSX parsing should succeed: {:?}",
6018            result.err()
6019        );
6020    }
6021
6022    // --- resolve_symbol ---
6023
6024    #[test]
6025    fn resolve_symbol_finds_match() {
6026        let provider = TreeSitterProvider::new();
6027        let matches = provider
6028            .resolve_symbol(&fixture_path("sample.ts"), "greet")
6029            .unwrap();
6030        assert_eq!(matches.len(), 1);
6031        assert_eq!(matches[0].symbol.name, "greet");
6032        assert_eq!(matches[0].symbol.kind, SymbolKind::Function);
6033    }
6034
6035    #[test]
6036    fn resolve_symbol_not_found() {
6037        let provider = TreeSitterProvider::new();
6038        let result = provider.resolve_symbol(&fixture_path("sample.ts"), "nonexistent");
6039        assert!(matches!(result, Err(AftError::SymbolNotFound { .. })));
6040    }
6041
6042    #[test]
6043    fn resolve_symbol_follows_reexport_chains() {
6044        let dir = tempfile::tempdir().unwrap();
6045        let config = dir.path().join("config.ts");
6046        let barrel1 = dir.path().join("barrel1.ts");
6047        let barrel2 = dir.path().join("barrel2.ts");
6048        let barrel3 = dir.path().join("barrel3.ts");
6049        let index = dir.path().join("index.ts");
6050
6051        std::fs::write(
6052            &config,
6053            "export class Config {}\nexport default class DefaultConfig {}\n",
6054        )
6055        .unwrap();
6056        std::fs::write(
6057            &barrel1,
6058            "export { Config } from './config';\nexport { default as NamedDefault } from './config';\n",
6059        )
6060        .unwrap();
6061        std::fs::write(
6062            &barrel2,
6063            "export { Config as RenamedConfig } from './barrel1';\n",
6064        )
6065        .unwrap();
6066        std::fs::write(
6067            &barrel3,
6068            "export * from './barrel2';\nexport * from './barrel1';\n",
6069        )
6070        .unwrap();
6071        std::fs::write(
6072            &index,
6073            "export class Config {}\nexport { RenamedConfig as FinalConfig } from './barrel3';\nexport * from './barrel3';\n",
6074        )
6075        .unwrap();
6076
6077        let provider = TreeSitterProvider::new();
6078        let config_canon = std::fs::canonicalize(&config).unwrap();
6079
6080        let direct = provider.resolve_symbol(&barrel1, "Config").unwrap();
6081        assert_eq!(direct.len(), 1);
6082        assert_eq!(direct[0].symbol.name, "Config");
6083        assert_eq!(direct[0].file, config_canon.display().to_string());
6084
6085        let renamed = provider.resolve_symbol(&barrel2, "RenamedConfig").unwrap();
6086        assert_eq!(renamed.len(), 1);
6087        assert_eq!(renamed[0].symbol.name, "Config");
6088        assert_eq!(renamed[0].file, config_canon.display().to_string());
6089
6090        let wildcard_chain = provider.resolve_symbol(&index, "FinalConfig").unwrap();
6091        assert_eq!(wildcard_chain.len(), 1);
6092        assert_eq!(wildcard_chain[0].symbol.name, "Config");
6093        assert_eq!(wildcard_chain[0].file, config_canon.display().to_string());
6094
6095        let wildcard_default = provider.resolve_symbol(&index, "NamedDefault").unwrap();
6096        assert_eq!(wildcard_default.len(), 1);
6097        assert_eq!(wildcard_default[0].symbol.name, "DefaultConfig");
6098        assert_eq!(wildcard_default[0].file, config_canon.display().to_string());
6099
6100        let local = provider.resolve_symbol(&index, "Config").unwrap();
6101        assert_eq!(local.len(), 1);
6102        assert_eq!(local[0].symbol.name, "Config");
6103        assert_eq!(local[0].file, index.display().to_string());
6104    }
6105
6106    // --- Parse tree caching ---
6107
6108    #[test]
6109    fn symbol_range_includes_rust_attributes() {
6110        let dir = tempfile::tempdir().unwrap();
6111        let path = dir.path().join("test_attrs.rs");
6112        std::fs::write(
6113            &path,
6114            "/// This is a doc comment\n#[test]\n#[cfg(test)]\nfn my_test_fn() {\n    assert!(true);\n}\n",
6115        )
6116        .unwrap();
6117
6118        let provider = TreeSitterProvider::new();
6119        let matches = provider.resolve_symbol(&path, "my_test_fn").unwrap();
6120        assert_eq!(matches.len(), 1);
6121        assert_eq!(
6122            matches[0].symbol.range.start_line, 0,
6123            "symbol range should include preceding /// doc comment, got start_line={}",
6124            matches[0].symbol.range.start_line
6125        );
6126    }
6127
6128    #[test]
6129    fn symbol_range_includes_go_doc_comment() {
6130        let dir = tempfile::tempdir().unwrap();
6131        let path = dir.path().join("test_doc.go");
6132        std::fs::write(
6133            &path,
6134            "package main\n\n// MyFunc does something useful.\n// It has a multi-line doc.\nfunc MyFunc() {\n}\n",
6135        )
6136        .unwrap();
6137
6138        let provider = TreeSitterProvider::new();
6139        let matches = provider.resolve_symbol(&path, "MyFunc").unwrap();
6140        assert_eq!(matches.len(), 1);
6141        assert_eq!(
6142            matches[0].symbol.range.start_line, 2,
6143            "symbol range should include preceding doc comments, got start_line={}",
6144            matches[0].symbol.range.start_line
6145        );
6146    }
6147
6148    #[test]
6149    fn symbol_range_skips_unrelated_comments() {
6150        let dir = tempfile::tempdir().unwrap();
6151        let path = dir.path().join("test_gap.go");
6152        std::fs::write(
6153            &path,
6154            "package main\n\n// This is a standalone comment\n\nfunc Standalone() {\n}\n",
6155        )
6156        .unwrap();
6157
6158        let provider = TreeSitterProvider::new();
6159        let matches = provider.resolve_symbol(&path, "Standalone").unwrap();
6160        assert_eq!(matches.len(), 1);
6161        assert_eq!(
6162            matches[0].symbol.range.start_line, 4,
6163            "symbol range should NOT include comment separated by blank line, got start_line={}",
6164            matches[0].symbol.range.start_line
6165        );
6166    }
6167
6168    #[test]
6169    fn parse_cache_returns_same_tree() {
6170        let mut parser = FileParser::new();
6171        let path = fixture_path("sample.ts");
6172
6173        let (tree1, _) = parser.parse(&path).unwrap();
6174        let tree1_root = tree1.root_node().byte_range();
6175
6176        let (tree2, _) = parser.parse(&path).unwrap();
6177        let tree2_root = tree2.root_node().byte_range();
6178
6179        // Same tree (cache hit) should return identical root node range
6180        assert_eq!(tree1_root, tree2_root);
6181    }
6182
6183    #[test]
6184    fn extract_symbols_from_tree_matches_list_symbols() {
6185        let path = fixture_path("sample.rs");
6186        let source = std::fs::read_to_string(&path).unwrap();
6187
6188        let provider = TreeSitterProvider::new();
6189        let listed = provider.list_symbols(&path).unwrap();
6190
6191        let mut parser = FileParser::new();
6192        let (tree, lang) = parser.parse(&path).unwrap();
6193        let extracted = extract_symbols_from_tree(&source, tree, lang).unwrap();
6194
6195        assert_eq!(symbols_as_debug(&extracted), symbols_as_debug(&listed));
6196    }
6197
6198    fn symbols_as_debug(symbols: &[Symbol]) -> Vec<String> {
6199        symbols
6200            .iter()
6201            .map(|symbol| {
6202                format!(
6203                    "{}|{:?}|{}:{}-{}:{}|{:?}|{:?}|{}|{:?}",
6204                    symbol.name,
6205                    symbol.kind,
6206                    symbol.range.start_line,
6207                    symbol.range.start_col,
6208                    symbol.range.end_line,
6209                    symbol.range.end_col,
6210                    symbol.signature,
6211                    symbol.scope_chain,
6212                    symbol.exported,
6213                    symbol.parent,
6214                )
6215            })
6216            .collect()
6217    }
6218
6219    // --- Python extraction ---
6220
6221    #[test]
6222    fn py_extracts_all_symbols() {
6223        let provider = TreeSitterProvider::new();
6224        let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
6225
6226        let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
6227        assert!(
6228            names.contains(&"top_level_function"),
6229            "missing top_level_function: {:?}",
6230            names
6231        );
6232        assert!(names.contains(&"MyClass"), "missing MyClass: {:?}", names);
6233        assert!(
6234            names.contains(&"instance_method"),
6235            "missing method instance_method: {:?}",
6236            names
6237        );
6238        assert!(
6239            names.contains(&"decorated_function"),
6240            "missing decorated_function: {:?}",
6241            names
6242        );
6243
6244        // Plan requires ≥4 symbols
6245        assert!(
6246            symbols.len() >= 4,
6247            "expected ≥4 symbols, got {}: {:?}",
6248            symbols.len(),
6249            names
6250        );
6251    }
6252
6253    #[test]
6254    fn py_symbol_kinds_correct() {
6255        let provider = TreeSitterProvider::new();
6256        let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
6257
6258        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
6259
6260        assert_eq!(find("top_level_function").kind, SymbolKind::Function);
6261        assert_eq!(find("MyClass").kind, SymbolKind::Class);
6262        assert_eq!(find("instance_method").kind, SymbolKind::Method);
6263        assert_eq!(find("decorated_function").kind, SymbolKind::Function);
6264        assert_eq!(find("OuterClass").kind, SymbolKind::Class);
6265        assert_eq!(find("InnerClass").kind, SymbolKind::Class);
6266        assert_eq!(find("inner_method").kind, SymbolKind::Method);
6267        assert_eq!(find("outer_method").kind, SymbolKind::Method);
6268    }
6269
6270    #[test]
6271    fn py_method_scope_chain() {
6272        let provider = TreeSitterProvider::new();
6273        let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
6274
6275        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
6276
6277        // Method inside MyClass
6278        assert_eq!(
6279            find("instance_method").scope_chain,
6280            vec!["MyClass"],
6281            "instance_method should have MyClass in scope chain"
6282        );
6283        assert_eq!(find("instance_method").parent.as_deref(), Some("MyClass"));
6284
6285        // Method inside OuterClass > InnerClass
6286        assert_eq!(
6287            find("inner_method").scope_chain,
6288            vec!["OuterClass", "InnerClass"],
6289            "inner_method should have nested scope chain"
6290        );
6291
6292        // InnerClass itself should have OuterClass in scope
6293        assert_eq!(
6294            find("InnerClass").scope_chain,
6295            vec!["OuterClass"],
6296            "InnerClass should have OuterClass in scope"
6297        );
6298
6299        // Top-level function has empty scope
6300        assert!(
6301            find("top_level_function").scope_chain.is_empty(),
6302            "top-level function should have empty scope chain"
6303        );
6304    }
6305
6306    #[test]
6307    fn py_decorated_function_signature() {
6308        let provider = TreeSitterProvider::new();
6309        let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
6310
6311        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
6312
6313        let sig = find("decorated_function").signature.as_ref().unwrap();
6314        assert!(
6315            sig.contains("@staticmethod"),
6316            "decorated function signature should include decorator: {}",
6317            sig
6318        );
6319        assert!(
6320            sig.contains("def decorated_function"),
6321            "signature should include function def: {}",
6322            sig
6323        );
6324    }
6325
6326    #[test]
6327    fn py_ranges_valid() {
6328        let provider = TreeSitterProvider::new();
6329        let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
6330
6331        for s in &symbols {
6332            assert!(
6333                s.range.end_line >= s.range.start_line,
6334                "symbol {} has invalid range: {:?}",
6335                s.name,
6336                s.range
6337            );
6338        }
6339    }
6340
6341    // --- Rust extraction ---
6342
6343    #[test]
6344    fn rs_extracts_all_symbols() {
6345        let provider = TreeSitterProvider::new();
6346        let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
6347
6348        let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
6349        assert!(
6350            names.contains(&"public_function"),
6351            "missing public_function: {:?}",
6352            names
6353        );
6354        assert!(
6355            names.contains(&"private_function"),
6356            "missing private_function: {:?}",
6357            names
6358        );
6359        assert!(names.contains(&"MyStruct"), "missing MyStruct: {:?}", names);
6360        assert!(names.contains(&"Color"), "missing enum Color: {:?}", names);
6361        assert!(
6362            names.contains(&"Drawable"),
6363            "missing trait Drawable: {:?}",
6364            names
6365        );
6366        // impl methods
6367        assert!(
6368            names.contains(&"new"),
6369            "missing impl method new: {:?}",
6370            names
6371        );
6372
6373        // Plan requires ≥6 symbols
6374        assert!(
6375            symbols.len() >= 6,
6376            "expected ≥6 symbols, got {}: {:?}",
6377            symbols.len(),
6378            names
6379        );
6380    }
6381
6382    #[test]
6383    fn rs_symbol_kinds_correct() {
6384        let provider = TreeSitterProvider::new();
6385        let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
6386
6387        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
6388
6389        assert_eq!(find("public_function").kind, SymbolKind::Function);
6390        assert_eq!(find("private_function").kind, SymbolKind::Function);
6391        assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
6392        assert_eq!(find("Color").kind, SymbolKind::Enum);
6393        assert_eq!(find("Drawable").kind, SymbolKind::Interface); // trait → Interface
6394        assert_eq!(find("new").kind, SymbolKind::Method);
6395    }
6396
6397    #[test]
6398    fn rs_pub_export_detection() {
6399        let provider = TreeSitterProvider::new();
6400        let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
6401
6402        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
6403
6404        assert!(
6405            find("public_function").exported,
6406            "pub fn should be exported"
6407        );
6408        assert!(
6409            !find("private_function").exported,
6410            "non-pub fn should not be exported"
6411        );
6412        assert!(find("MyStruct").exported, "pub struct should be exported");
6413        assert!(find("Color").exported, "pub enum should be exported");
6414        assert!(find("Drawable").exported, "pub trait should be exported");
6415        assert!(
6416            find("new").exported,
6417            "pub fn inside impl should be exported"
6418        );
6419        assert!(
6420            !find("helper").exported,
6421            "non-pub fn inside impl should not be exported"
6422        );
6423    }
6424
6425    #[test]
6426    fn rs_impl_method_scope_chain() {
6427        let provider = TreeSitterProvider::new();
6428        let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
6429
6430        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
6431
6432        // `impl MyStruct { fn new() }` → scope chain = ["MyStruct"]
6433        assert_eq!(
6434            find("new").scope_chain,
6435            vec!["MyStruct"],
6436            "impl method should have type in scope chain"
6437        );
6438        assert_eq!(find("new").parent.as_deref(), Some("MyStruct"));
6439
6440        // Free function has empty scope chain
6441        assert!(
6442            find("public_function").scope_chain.is_empty(),
6443            "free function should have empty scope chain"
6444        );
6445    }
6446
6447    #[test]
6448    fn rs_trait_impl_scope_chain() {
6449        let provider = TreeSitterProvider::new();
6450        let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
6451
6452        // `impl Drawable for MyStruct { fn draw() }` → scope = ["Drawable for MyStruct"]
6453        let draw = symbols.iter().find(|s| s.name == "draw").unwrap();
6454        assert_eq!(
6455            draw.scope_chain,
6456            vec!["Drawable for MyStruct"],
6457            "trait impl method should have 'Trait for Type' scope"
6458        );
6459        assert_eq!(draw.parent.as_deref(), Some("MyStruct"));
6460    }
6461
6462    #[test]
6463    fn rs_ranges_valid() {
6464        let provider = TreeSitterProvider::new();
6465        let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
6466
6467        for s in &symbols {
6468            assert!(
6469                s.range.end_line >= s.range.start_line,
6470                "symbol {} has invalid range: {:?}",
6471                s.name,
6472                s.range
6473            );
6474        }
6475    }
6476
6477    // --- Go extraction ---
6478
6479    #[test]
6480    fn go_extracts_all_symbols() {
6481        let provider = TreeSitterProvider::new();
6482        let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
6483
6484        let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
6485        assert!(
6486            names.contains(&"ExportedFunction"),
6487            "missing ExportedFunction: {:?}",
6488            names
6489        );
6490        assert!(
6491            names.contains(&"unexportedFunction"),
6492            "missing unexportedFunction: {:?}",
6493            names
6494        );
6495        assert!(
6496            names.contains(&"MyStruct"),
6497            "missing struct MyStruct: {:?}",
6498            names
6499        );
6500        assert!(
6501            names.contains(&"Reader"),
6502            "missing interface Reader: {:?}",
6503            names
6504        );
6505        // receiver method
6506        assert!(
6507            names.contains(&"String"),
6508            "missing receiver method String: {:?}",
6509            names
6510        );
6511
6512        // Plan requires ≥4 symbols
6513        assert!(
6514            symbols.len() >= 4,
6515            "expected ≥4 symbols, got {}: {:?}",
6516            symbols.len(),
6517            names
6518        );
6519    }
6520
6521    #[test]
6522    fn go_symbol_kinds_correct() {
6523        let provider = TreeSitterProvider::new();
6524        let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
6525
6526        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
6527
6528        assert_eq!(find("ExportedFunction").kind, SymbolKind::Function);
6529        assert_eq!(find("unexportedFunction").kind, SymbolKind::Function);
6530        assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
6531        assert_eq!(find("Reader").kind, SymbolKind::Interface);
6532        assert_eq!(find("String").kind, SymbolKind::Method);
6533        assert_eq!(find("helper").kind, SymbolKind::Method);
6534    }
6535
6536    #[test]
6537    fn go_uppercase_export_detection() {
6538        let provider = TreeSitterProvider::new();
6539        let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
6540
6541        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
6542
6543        assert!(
6544            find("ExportedFunction").exported,
6545            "ExportedFunction (uppercase) should be exported"
6546        );
6547        assert!(
6548            !find("unexportedFunction").exported,
6549            "unexportedFunction (lowercase) should not be exported"
6550        );
6551        assert!(
6552            find("MyStruct").exported,
6553            "MyStruct (uppercase) should be exported"
6554        );
6555        assert!(
6556            find("Reader").exported,
6557            "Reader (uppercase) should be exported"
6558        );
6559        assert!(
6560            find("String").exported,
6561            "String method (uppercase) should be exported"
6562        );
6563        assert!(
6564            !find("helper").exported,
6565            "helper method (lowercase) should not be exported"
6566        );
6567    }
6568
6569    #[test]
6570    fn go_receiver_method_scope_chain() {
6571        let provider = TreeSitterProvider::new();
6572        let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
6573
6574        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
6575
6576        // `func (m *MyStruct) String()` → scope chain = ["MyStruct"]
6577        assert_eq!(
6578            find("String").scope_chain,
6579            vec!["MyStruct"],
6580            "receiver method should have type in scope chain"
6581        );
6582        assert_eq!(find("String").parent.as_deref(), Some("MyStruct"));
6583
6584        // Regular function has empty scope chain
6585        assert!(
6586            find("ExportedFunction").scope_chain.is_empty(),
6587            "regular function should have empty scope chain"
6588        );
6589    }
6590
6591    #[test]
6592    fn go_ranges_valid() {
6593        let provider = TreeSitterProvider::new();
6594        let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
6595
6596        for s in &symbols {
6597            assert!(
6598                s.range.end_line >= s.range.start_line,
6599                "symbol {} has invalid range: {:?}",
6600                s.name,
6601                s.range
6602            );
6603        }
6604    }
6605
6606    // --- Cross-language ---
6607
6608    #[test]
6609    fn cross_language_all_six_produce_symbols() {
6610        let provider = TreeSitterProvider::new();
6611
6612        let fixtures = [
6613            ("sample.ts", "TypeScript"),
6614            ("sample.tsx", "TSX"),
6615            ("sample.js", "JavaScript"),
6616            ("sample.py", "Python"),
6617            ("sample.rs", "Rust"),
6618            ("sample.go", "Go"),
6619        ];
6620
6621        for (fixture, lang) in &fixtures {
6622            let symbols = provider
6623                .list_symbols(&fixture_path(fixture))
6624                .unwrap_or_else(|e| panic!("{} ({}) failed: {:?}", lang, fixture, e));
6625            assert!(
6626                symbols.len() >= 2,
6627                "{} should produce ≥2 symbols, got {}: {:?}",
6628                lang,
6629                symbols.len(),
6630                symbols.iter().map(|s| &s.name).collect::<Vec<_>>()
6631            );
6632        }
6633    }
6634
6635    // --- Symbol cache tests ---
6636
6637    #[test]
6638    fn symbol_cache_returns_cached_results_on_second_call() {
6639        let dir = tempfile::tempdir().unwrap();
6640        let file = dir.path().join("test.rs");
6641        std::fs::write(&file, "pub fn hello() {}\npub fn world() {}").unwrap();
6642
6643        let mut parser = FileParser::new();
6644
6645        let symbols1 = parser.extract_symbols(&file).unwrap();
6646        assert_eq!(symbols1.len(), 2);
6647
6648        // Second call should return cached result
6649        let symbols2 = parser.extract_symbols(&file).unwrap();
6650        assert_eq!(symbols2.len(), 2);
6651        assert_eq!(symbols1[0].name, symbols2[0].name);
6652
6653        // Verify cache is populated
6654        assert!(parser.symbol_cache.read().unwrap().contains_key(&file));
6655    }
6656
6657    #[test]
6658    fn symbol_cache_invalidates_on_file_change() {
6659        let dir = tempfile::tempdir().unwrap();
6660        let file = dir.path().join("test.rs");
6661        std::fs::write(&file, "pub fn hello() {}").unwrap();
6662
6663        let mut parser = FileParser::new();
6664
6665        let symbols1 = parser.extract_symbols(&file).unwrap();
6666        assert_eq!(symbols1.len(), 1);
6667        assert_eq!(symbols1[0].name, "hello");
6668
6669        // Wait to ensure mtime changes (filesystem resolution can be 1s on some OS)
6670        std::thread::sleep(std::time::Duration::from_millis(50));
6671
6672        // Modify file — add a second function
6673        std::fs::write(&file, "pub fn hello() {}\npub fn goodbye() {}").unwrap();
6674
6675        // Should detect mtime change and re-extract
6676        let symbols2 = parser.extract_symbols(&file).unwrap();
6677        assert_eq!(symbols2.len(), 2);
6678        assert!(symbols2.iter().any(|s| s.name == "goodbye"));
6679    }
6680
6681    #[test]
6682    fn symbol_cache_invalidate_method_clears_entry() {
6683        let dir = tempfile::tempdir().unwrap();
6684        let file = dir.path().join("test.rs");
6685        std::fs::write(&file, "pub fn hello() {}").unwrap();
6686
6687        let mut parser = FileParser::new();
6688        parser.extract_symbols(&file).unwrap();
6689        assert!(parser.symbol_cache.read().unwrap().contains_key(&file));
6690
6691        parser.invalidate_symbols(&file);
6692        assert!(!parser.symbol_cache.read().unwrap().contains_key(&file));
6693        // Parse tree cache should also be cleared
6694        assert!(!parser.cache.contains_key(&file));
6695    }
6696
6697    #[test]
6698    fn symbol_cache_works_for_multiple_languages() {
6699        let dir = tempfile::tempdir().unwrap();
6700        let rs_file = dir.path().join("lib.rs");
6701        let ts_file = dir.path().join("app.ts");
6702        let py_file = dir.path().join("main.py");
6703
6704        std::fs::write(&rs_file, "pub fn rust_fn() {}").unwrap();
6705        std::fs::write(&ts_file, "export function tsFn() {}").unwrap();
6706        std::fs::write(&py_file, "def py_fn():\n    pass").unwrap();
6707
6708        let mut parser = FileParser::new();
6709
6710        let rs_syms = parser.extract_symbols(&rs_file).unwrap();
6711        let ts_syms = parser.extract_symbols(&ts_file).unwrap();
6712        let py_syms = parser.extract_symbols(&py_file).unwrap();
6713
6714        assert!(rs_syms.iter().any(|s| s.name == "rust_fn"));
6715        assert!(ts_syms.iter().any(|s| s.name == "tsFn"));
6716        assert!(py_syms.iter().any(|s| s.name == "py_fn"));
6717
6718        // All should be cached now
6719        assert_eq!(parser.symbol_cache.read().unwrap().len(), 3);
6720
6721        // Re-extract should return same results from cache
6722        let rs_syms2 = parser.extract_symbols(&rs_file).unwrap();
6723        assert_eq!(rs_syms.len(), rs_syms2.len());
6724    }
6725
6726    #[test]
6727    fn extract_json_symbols_top_level_keys() {
6728        let dir = tempfile::tempdir().unwrap();
6729        let file = dir.path().join("package.json");
6730        std::fs::write(&file, r#"{"name": "x", "version": "1"}"#).unwrap();
6731
6732        let mut parser = FileParser::new();
6733        let symbols = parser.extract_symbols(&file).unwrap();
6734
6735        assert_eq!(symbols.len(), 2);
6736        assert!(symbols
6737            .iter()
6738            .any(|s| s.name == "name" && s.kind == SymbolKind::Variable));
6739        assert!(symbols
6740            .iter()
6741            .any(|s| s.name == "version" && s.kind == SymbolKind::Variable));
6742    }
6743
6744    #[test]
6745    fn extract_json_symbols_root_array() {
6746        let dir = tempfile::tempdir().unwrap();
6747        let file = dir.path().join("array.json");
6748        std::fs::write(&file, "[1,2,3]").unwrap();
6749
6750        let mut parser = FileParser::new();
6751        let symbols = parser.extract_symbols(&file).unwrap();
6752
6753        assert_eq!(symbols.len(), 0);
6754    }
6755
6756    #[test]
6757    fn extract_json_symbols_no_recursion_into_nested() {
6758        let dir = tempfile::tempdir().unwrap();
6759        let file = dir.path().join("nested.json");
6760        std::fs::write(&file, r#"{"scripts": {"build": "tsc"}}"#).unwrap();
6761
6762        let mut parser = FileParser::new();
6763        let symbols = parser.extract_symbols(&file).unwrap();
6764
6765        assert_eq!(symbols.len(), 1);
6766        assert_eq!(symbols[0].name, "scripts");
6767        assert!(!symbols.iter().any(|s| s.name == "build"));
6768    }
6769
6770    #[test]
6771    fn extract_scala_symbols_object_and_method() {
6772        let dir = tempfile::tempdir().unwrap();
6773        let file = dir.path().join("Greeter.scala");
6774        std::fs::write(
6775            &file,
6776            "object Greeter {\n  def hello(name: String): String = s\"hi $name\"\n}",
6777        )
6778        .unwrap();
6779
6780        let mut parser = FileParser::new();
6781        let symbols = parser.extract_symbols(&file).unwrap();
6782
6783        assert!(symbols
6784            .iter()
6785            .any(|s| s.name == "Greeter" && s.kind == SymbolKind::Class));
6786        assert!(symbols.iter().any(|s| s.name == "hello"
6787            && s.kind == SymbolKind::Method
6788            && s.scope_chain == vec!["Greeter".to_string()]));
6789    }
6790
6791    #[test]
6792    fn extract_scala_symbols_class_and_trait() {
6793        let dir = tempfile::tempdir().unwrap();
6794        let file = dir.path().join("Types.scala");
6795        std::fs::write(&file, "class Foo\ntrait Bar").unwrap();
6796
6797        let mut parser = FileParser::new();
6798        let symbols = parser.extract_symbols(&file).unwrap();
6799
6800        assert!(symbols
6801            .iter()
6802            .any(|s| s.name == "Foo" && s.kind == SymbolKind::Class));
6803        assert!(symbols
6804            .iter()
6805            .any(|s| s.name == "Bar" && s.kind == SymbolKind::Interface));
6806    }
6807}