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