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