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.decl) @arrow.def
30(lexical_declaration
31  (variable_declarator
32    name: (identifier) @arrow.name
33    value: (function_expression) @arrow.body) @arrow.decl) @arrow.def
34(lexical_declaration
35  (variable_declarator
36    name: (identifier) @arrow.name
37    value: (generator_function) @arrow.body) @arrow.decl) @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.decl) @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.decl) @arrow.def
89(lexical_declaration
90  (variable_declarator
91    name: (identifier) @arrow.name
92    value: (function_expression) @arrow.body) @arrow.decl) @arrow.def
93(lexical_declaration
94  (variable_declarator
95    name: (identifier) @arrow.name
96    value: (generator_function) @arrow.body) @arrow.decl) @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.decl) @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 PASCAL_QUERY: &str = r#"
395;; program / unit
396(program (moduleName (identifier) @program.name)) @program.def
397(unit (moduleName (identifier) @unit.name)) @unit.def
398
399;; type declarations
400(declType (identifier) @type.name) @type.def
401
402;; const / var declarations
403(declConst (identifier) @const.name) @const.def
404(declVar (identifier) @var.name) @var.def
405
406;; procedure / function definitions (implementation)
407(defProc
408  (declProc
409    [
410      (identifier) @proc.name
411      (genericDot) @proc.name
412    ])) @proc.def
413
414;; procedure / function declarations (interface / class)
415(declProc
416  [
417    (identifier) @proc.name
418    (genericDot) @proc.name
419  ]) @proc.def
420"#;
421
422const SCSS_QUERY: &str = r#"
423;; SCSS definitions
424(mixin_statement
425  name: (identifier) @mixin.name) @mixin.def
426
427(function_statement
428  name: (identifier) @fn.name) @fn.def
429
430(declaration
431  (property_name) @var.name) @var.def
432
433(rule_set
434  (selectors) @selector.name) @selector.def
435"#;
436
437const SCALA_QUERY: &str = r#"
438;; classes / objects / traits
439(class_definition
440  name: (identifier) @class.name) @class.def
441(object_definition
442  name: (identifier) @object.name) @object.def
443(enum_definition
444  name: (_) @enum.name) @enum.def
445(trait_definition
446  name: (identifier) @trait.name) @trait.def
447;; methods (def)
448(function_definition
449  name: (identifier) @fn.name) @fn.def
450(function_declaration
451  name: (identifier) @fn.name) @fn.def
452;; vals / vars / type aliases
453(val_definition
454  pattern: (identifier) @val.name) @val.def
455(var_definition
456  pattern: (identifier) @var.name) @var.def
457(given_definition
458  name: (_) @given.name) @given.def
459(type_definition
460  name: (type_identifier) @type.name) @type.def
461"#;
462
463const JAVA_QUERY: &str = r#"
464;; types
465(class_declaration
466  name: (identifier) @class.name) @class.def
467(interface_declaration
468  name: (identifier) @interface.name) @interface.def
469(annotation_type_declaration
470  name: (identifier) @interface.name) @interface.def
471(enum_declaration
472  name: (identifier) @enum.name) @enum.def
473(record_declaration
474  name: (identifier) @struct.name) @struct.def
475
476;; members
477(method_declaration
478  name: (identifier) @fn.name) @fn.def
479(constructor_declaration
480  name: (identifier) @fn.name) @fn.def
481(field_declaration
482  declarator: (variable_declarator
483    name: (identifier) @var.name)) @var.def
484"#;
485
486const RUBY_QUERY: &str = r#"
487;; modules / classes
488(module
489  name: (constant) @module.name) @module.def
490(class
491  name: (constant) @class.name) @class.def
492
493;; methods
494(method
495  name: (_) @fn.name) @fn.def
496(singleton_method
497  name: (_) @fn.name) @fn.def
498
499;; constants
500(assignment
501  left: (constant) @var.name) @var.def
502"#;
503
504const KOTLIN_QUERY: &str = r#"
505;; declarations
506(class_declaration
507  (type_identifier) @class.name) @class.def
508(object_declaration
509  (type_identifier) @object.name) @object.def
510(function_declaration
511  (simple_identifier) @fn.name) @fn.def
512(property_declaration
513  (variable_declaration
514    (simple_identifier) @var.name)) @var.def
515(type_alias
516  (type_identifier) @type.name) @type.def
517"#;
518
519const SWIFT_QUERY: &str = r#"
520;; types
521(class_declaration
522  name: (type_identifier) @class.name) @class.def
523(protocol_declaration
524  name: (type_identifier) @interface.name) @interface.def
525
526;; functions and members
527(function_declaration
528  name: (simple_identifier) @fn.name) @fn.def
529(protocol_function_declaration
530  name: (simple_identifier) @fn.name) @fn.def
531(property_declaration
532  name: (pattern
533    bound_identifier: (simple_identifier) @var.name)) @var.def
534(typealias_declaration
535  name: (type_identifier) @type.name) @type.def
536"#;
537
538const PHP_QUERY: &str = r#"
539;; namespaces and types
540(namespace_definition
541  name: (namespace_name) @namespace.name) @namespace.def
542(class_declaration
543  name: (name) @class.name) @class.def
544(interface_declaration
545  name: (name) @interface.name) @interface.def
546(trait_declaration
547  name: (name) @trait.name) @trait.def
548(enum_declaration
549  name: (name) @enum.name) @enum.def
550
551;; functions and members
552(function_definition
553  name: (name) @fn.name) @fn.def
554(method_declaration
555  name: (name) @fn.name) @fn.def
556(property_declaration
557  (property_element
558    name: (variable_name (name) @var.name))) @var.def
559"#;
560
561const LUA_QUERY: &str = r#"
562;; functions
563(function_declaration
564  name: (identifier) @fn.name) @fn.def
565(function_declaration
566  name: (dot_index_expression
567    field: (identifier) @fn.name)) @fn.def
568(function_declaration
569  name: (method_index_expression
570    method: (identifier) @fn.name)) @fn.def
571
572;; locals / module tables
573(variable_declaration
574  (assignment_statement
575    (variable_list
576      name: (identifier) @var.name))) @var.def
577(variable_declaration
578  (assignment_statement
579    (variable_list
580      name: (variable) @var.name))) @var.def
581(variable_declaration
582  (variable_list
583    name: (identifier) @var.name)) @var.def
584(variable_declaration
585  (variable_list
586    name: (variable) @var.name)) @var.def
587"#;
588
589const PERL_QUERY: &str = r#"
590;; packages and subroutines
591(package_statement
592  (package_name) @package.name) @package.def
593(function_definition
594  name: (identifier) @fn.name) @fn.def
595(function_definition_without_sub
596  name: (identifier) @fn.name) @fn.def
597
598;; constants / lexical variables
599(use_constant_statement
600  constant: (identifier) @var.name) @var.def
601(variable_declaration
602  variable_name: (_) @var.name) @var.def
603"#;
604
605/// Supported language identifier.
606#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
607pub enum LangId {
608    TypeScript,
609    Tsx,
610    JavaScript,
611    Python,
612    Rust,
613    Go,
614    C,
615    Cpp,
616    Zig,
617    CSharp,
618    Bash,
619    Html,
620    Markdown,
621    Solidity,
622    Scss,
623    Vue,
624    Json,
625    Scala,
626    Java,
627    Ruby,
628    Kotlin,
629    Swift,
630    Php,
631    Lua,
632    Perl,
633    Yaml,
634    Pascal,
635}
636
637/// Maps file extension to language identifier.
638pub fn detect_language(path: &Path) -> Option<LangId> {
639    let ext = path.extension()?.to_str()?;
640    match ext {
641        "ts" | "mts" | "cts" => Some(LangId::TypeScript),
642        "tsx" => Some(LangId::Tsx),
643        "js" | "jsx" | "mjs" | "cjs" => Some(LangId::JavaScript),
644        "py" | "pyi" => Some(LangId::Python),
645        "rs" => Some(LangId::Rust),
646        "go" => Some(LangId::Go),
647        "c" | "h" => Some(LangId::C),
648        "cc" | "cpp" | "cxx" | "hpp" | "hh" => Some(LangId::Cpp),
649        "zig" => Some(LangId::Zig),
650        "cs" => Some(LangId::CSharp),
651        "sh" | "bash" | "zsh" => Some(LangId::Bash),
652        "html" | "htm" => Some(LangId::Html),
653        "md" | "markdown" | "mdx" => Some(LangId::Markdown),
654        "sol" => Some(LangId::Solidity),
655        "scss" => Some(LangId::Scss),
656        "vue" => Some(LangId::Vue),
657        "json" | "jsonc" => Some(LangId::Json),
658        "scala" | "sc" => Some(LangId::Scala),
659        "java" => Some(LangId::Java),
660        "rb" => Some(LangId::Ruby),
661        "kt" | "kts" => Some(LangId::Kotlin),
662        "swift" => Some(LangId::Swift),
663        "inc" | "php" => Some(LangId::Php),
664        "lua" => Some(LangId::Lua),
665        "pl" | "pm" | "t" => Some(LangId::Perl),
666        "yaml" | "yml" => Some(LangId::Yaml),
667        "pas" | "pp" | "dpr" | "dpk" | "lpr" => Some(LangId::Pascal),
668        _ => None,
669    }
670}
671
672/// Returns the tree-sitter Language grammar for a given LangId.
673pub fn grammar_for(lang: LangId) -> Language {
674    match lang {
675        LangId::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
676        LangId::Tsx => tree_sitter_typescript::LANGUAGE_TSX.into(),
677        LangId::JavaScript => tree_sitter_javascript::LANGUAGE.into(),
678        LangId::Python => tree_sitter_python::LANGUAGE.into(),
679        LangId::Rust => tree_sitter_rust::LANGUAGE.into(),
680        LangId::Go => tree_sitter_go::LANGUAGE.into(),
681        LangId::C => tree_sitter_c::LANGUAGE.into(),
682        LangId::Cpp => tree_sitter_cpp::LANGUAGE.into(),
683        LangId::Zig => tree_sitter_zig::LANGUAGE.into(),
684        LangId::CSharp => tree_sitter_c_sharp::LANGUAGE.into(),
685        LangId::Bash => tree_sitter_bash::LANGUAGE.into(),
686        LangId::Html => tree_sitter_html::LANGUAGE.into(),
687        LangId::Markdown => tree_sitter_md::LANGUAGE.into(),
688        LangId::Solidity => tree_sitter_solidity::LANGUAGE.into(),
689        LangId::Scss => tree_sitter_scss::language(),
690        LangId::Vue => tree_sitter_vue::LANGUAGE.into(),
691        LangId::Json => tree_sitter_json::LANGUAGE.into(),
692        LangId::Scala => tree_sitter_scala::LANGUAGE.into(),
693        LangId::Java => tree_sitter_java::LANGUAGE.into(),
694        LangId::Ruby => tree_sitter_ruby::LANGUAGE.into(),
695        LangId::Kotlin => tree_sitter_kotlin_sg::LANGUAGE.into(),
696        LangId::Swift => tree_sitter_swift::LANGUAGE.into(),
697        LangId::Php => tree_sitter_php::LANGUAGE_PHP.into(),
698        LangId::Lua => tree_sitter_lua::LANGUAGE.into(),
699        LangId::Perl => tree_sitter_perl::LANGUAGE.into(),
700        LangId::Yaml => tree_sitter_yaml::LANGUAGE.into(),
701        LangId::Pascal => tree_sitter_pascal::LANGUAGE.into(),
702    }
703}
704
705/// Returns the query pattern string for a given LangId, if implemented.
706fn query_for(lang: LangId) -> Option<&'static str> {
707    match lang {
708        LangId::TypeScript | LangId::Tsx => Some(TS_QUERY),
709        LangId::JavaScript => Some(JS_QUERY),
710        LangId::Python => Some(PY_QUERY),
711        LangId::Rust => Some(RS_QUERY),
712        LangId::Go => Some(GO_QUERY),
713        LangId::C => Some(C_QUERY),
714        LangId::Cpp => Some(CPP_QUERY),
715        LangId::Zig => Some(ZIG_QUERY),
716        LangId::CSharp => Some(CSHARP_QUERY),
717        LangId::Bash => Some(BASH_QUERY),
718        LangId::Html => None, // HTML uses direct tree walking like Markdown
719        LangId::Markdown => None,
720        LangId::Solidity => Some(SOL_QUERY),
721        LangId::Scss => Some(SCSS_QUERY),
722        LangId::Vue => None,
723        LangId::Json => None,
724        LangId::Scala => Some(SCALA_QUERY),
725        LangId::Java => Some(JAVA_QUERY),
726        LangId::Ruby => Some(RUBY_QUERY),
727        LangId::Kotlin => Some(KOTLIN_QUERY),
728        LangId::Swift => Some(SWIFT_QUERY),
729        LangId::Php => Some(PHP_QUERY),
730        LangId::Lua => Some(LUA_QUERY),
731        LangId::Perl => Some(PERL_QUERY),
732        LangId::Yaml => None, // YAML uses direct tree walking like JSON
733        LangId::Pascal => Some(PASCAL_QUERY),
734    }
735}
736
737static TS_QUERY_CACHE: LazyLock<Result<Query, String>> =
738    LazyLock::new(|| compile_query(LangId::TypeScript));
739static TSX_QUERY_CACHE: LazyLock<Result<Query, String>> =
740    LazyLock::new(|| compile_query(LangId::Tsx));
741static JS_QUERY_CACHE: LazyLock<Result<Query, String>> =
742    LazyLock::new(|| compile_query(LangId::JavaScript));
743static PY_QUERY_CACHE: LazyLock<Result<Query, String>> =
744    LazyLock::new(|| compile_query(LangId::Python));
745static RS_QUERY_CACHE: LazyLock<Result<Query, String>> =
746    LazyLock::new(|| compile_query(LangId::Rust));
747static GO_QUERY_CACHE: LazyLock<Result<Query, String>> =
748    LazyLock::new(|| compile_query(LangId::Go));
749static C_QUERY_CACHE: LazyLock<Result<Query, String>> = LazyLock::new(|| compile_query(LangId::C));
750static CPP_QUERY_CACHE: LazyLock<Result<Query, String>> =
751    LazyLock::new(|| compile_query(LangId::Cpp));
752static ZIG_QUERY_CACHE: LazyLock<Result<Query, String>> =
753    LazyLock::new(|| compile_query(LangId::Zig));
754static CSHARP_QUERY_CACHE: LazyLock<Result<Query, String>> =
755    LazyLock::new(|| compile_query(LangId::CSharp));
756static BASH_QUERY_CACHE: LazyLock<Result<Query, String>> =
757    LazyLock::new(|| compile_query(LangId::Bash));
758static SOL_QUERY_CACHE: LazyLock<Result<Query, String>> =
759    LazyLock::new(|| compile_query(LangId::Solidity));
760static SCSS_QUERY_CACHE: LazyLock<Result<Query, String>> =
761    LazyLock::new(|| compile_query(LangId::Scss));
762static SCALA_QUERY_CACHE: LazyLock<Result<Query, String>> =
763    LazyLock::new(|| compile_query(LangId::Scala));
764static JAVA_QUERY_CACHE: LazyLock<Result<Query, String>> =
765    LazyLock::new(|| compile_query(LangId::Java));
766static RUBY_QUERY_CACHE: LazyLock<Result<Query, String>> =
767    LazyLock::new(|| compile_query(LangId::Ruby));
768static KOTLIN_QUERY_CACHE: LazyLock<Result<Query, String>> =
769    LazyLock::new(|| compile_query(LangId::Kotlin));
770static SWIFT_QUERY_CACHE: LazyLock<Result<Query, String>> =
771    LazyLock::new(|| compile_query(LangId::Swift));
772static PHP_QUERY_CACHE: LazyLock<Result<Query, String>> =
773    LazyLock::new(|| compile_query(LangId::Php));
774static LUA_QUERY_CACHE: LazyLock<Result<Query, String>> =
775    LazyLock::new(|| compile_query(LangId::Lua));
776static PERL_QUERY_CACHE: LazyLock<Result<Query, String>> =
777    LazyLock::new(|| compile_query(LangId::Perl));
778static PASCAL_QUERY_CACHE: LazyLock<Result<Query, String>> =
779    LazyLock::new(|| compile_query(LangId::Pascal));
780
781fn compile_query(lang: LangId) -> Result<Query, String> {
782    let query_src = query_for(lang).ok_or_else(|| format!("missing query for {lang:?}"))?;
783    let grammar = grammar_for(lang);
784    Query::new(&grammar, query_src)
785        .map_err(|error| format!("query compile error for {lang:?}: {error}"))
786}
787
788fn cached_query_for(lang: LangId) -> Result<Option<&'static Query>, AftError> {
789    let query = match lang {
790        LangId::TypeScript => Some(&*TS_QUERY_CACHE),
791        LangId::Tsx => Some(&*TSX_QUERY_CACHE),
792        LangId::JavaScript => Some(&*JS_QUERY_CACHE),
793        LangId::Python => Some(&*PY_QUERY_CACHE),
794        LangId::Rust => Some(&*RS_QUERY_CACHE),
795        LangId::Go => Some(&*GO_QUERY_CACHE),
796        LangId::C => Some(&*C_QUERY_CACHE),
797        LangId::Cpp => Some(&*CPP_QUERY_CACHE),
798        LangId::Zig => Some(&*ZIG_QUERY_CACHE),
799        LangId::CSharp => Some(&*CSHARP_QUERY_CACHE),
800        LangId::Bash => Some(&*BASH_QUERY_CACHE),
801        LangId::Solidity => Some(&*SOL_QUERY_CACHE),
802        LangId::Scss => Some(&*SCSS_QUERY_CACHE),
803        LangId::Scala => Some(&*SCALA_QUERY_CACHE),
804        LangId::Java => Some(&*JAVA_QUERY_CACHE),
805        LangId::Ruby => Some(&*RUBY_QUERY_CACHE),
806        LangId::Kotlin => Some(&*KOTLIN_QUERY_CACHE),
807        LangId::Swift => Some(&*SWIFT_QUERY_CACHE),
808        LangId::Php => Some(&*PHP_QUERY_CACHE),
809        LangId::Lua => Some(&*LUA_QUERY_CACHE),
810        LangId::Perl => Some(&*PERL_QUERY_CACHE),
811        LangId::Pascal => Some(&*PASCAL_QUERY_CACHE),
812        LangId::Html | LangId::Markdown | LangId::Vue | LangId::Json | LangId::Yaml => None,
813    };
814
815    query
816        .map(|result| {
817            result.as_ref().map_err(|message| AftError::ParseError {
818                message: message.clone(),
819            })
820        })
821        .transpose()
822}
823
824/// Cached parse result: mtime at parse time + the tree.
825struct CachedTree {
826    mtime: SystemTime,
827    size: u64,
828    content_hash: blake3::Hash,
829    tree: Tree,
830}
831
832/// Cached symbol extraction result: mtime at extraction time + symbols.
833#[derive(Clone)]
834struct CachedSymbols {
835    mtime: SystemTime,
836    size: u64,
837    content_hash: blake3::Hash,
838    symbols: Vec<Symbol>,
839}
840
841fn content_hash_for_source(source: &str) -> blake3::Hash {
842    if source.len() as u64 > cache_freshness::CONTENT_HASH_SIZE_CAP {
843        cache_freshness::zero_hash()
844    } else {
845        cache_freshness::hash_bytes(source.as_bytes())
846    }
847}
848
849fn cached_file_is_fresh(
850    path: &Path,
851    cached_mtime: SystemTime,
852    cached_size: u64,
853    cached_content_hash: blake3::Hash,
854    fallback_mtime: SystemTime,
855) -> bool {
856    let Ok(metadata) = std::fs::metadata(path) else {
857        return false;
858    };
859    let current_size = metadata.len();
860    if current_size != cached_size {
861        return false;
862    }
863
864    let current_mtime = metadata.modified().unwrap_or(fallback_mtime);
865    if current_size > cache_freshness::CONTENT_HASH_SIZE_CAP {
866        return current_mtime == cached_mtime;
867    }
868
869    matches!(
870        cache_freshness::hash_file_if_small(path, current_size),
871        Ok(Some(hash)) if hash == cached_content_hash
872    )
873}
874
875/// Shared symbol cache that can be pre-warmed in a background thread
876/// and read by all parser instances.
877#[derive(Clone, Default)]
878pub struct SymbolCache {
879    entries: HashMap<PathBuf, CachedSymbols>,
880    generation: u64,
881    project_root: Option<PathBuf>,
882}
883
884pub type SharedSymbolCache = Arc<RwLock<SymbolCache>>;
885
886impl SymbolCache {
887    pub fn new() -> Self {
888        Self {
889            entries: HashMap::new(),
890            generation: 0,
891            project_root: None,
892        }
893    }
894
895    /// Set the project root used for disk persistence.
896    pub fn set_project_root(&mut self, project_root: PathBuf) {
897        debug_assert!(project_root.is_absolute());
898        self.project_root = Some(project_root);
899    }
900
901    /// Set the project root only when the caller still belongs to the active
902    /// cache generation.
903    pub fn set_project_root_for_generation(
904        &mut self,
905        generation: u64,
906        project_root: PathBuf,
907    ) -> bool {
908        if self.generation != generation {
909            return false;
910        }
911        self.set_project_root(project_root);
912        true
913    }
914
915    /// Insert pre-warmed symbols for a file.
916    pub fn insert(
917        &mut self,
918        path: PathBuf,
919        mtime: SystemTime,
920        size: u64,
921        content_hash: blake3::Hash,
922        symbols: Vec<Symbol>,
923    ) {
924        self.entries.insert(
925            path,
926            CachedSymbols {
927                mtime,
928                size,
929                content_hash,
930                symbols,
931            },
932        );
933    }
934
935    /// Insert symbols only when the caller still belongs to the active cache generation.
936    pub fn insert_for_generation(
937        &mut self,
938        generation: u64,
939        path: PathBuf,
940        mtime: SystemTime,
941        size: u64,
942        content_hash: blake3::Hash,
943        symbols: Vec<Symbol>,
944    ) -> bool {
945        if self.generation != generation {
946            return false;
947        }
948        self.insert(path, mtime, size, content_hash, symbols);
949        true
950    }
951
952    /// Return cached symbols when the source file is still fresh.
953    pub fn get(&self, path: &Path, mtime: SystemTime) -> Option<Vec<Symbol>> {
954        self.entries.get(path).and_then(|cached| {
955            cached_file_is_fresh(path, cached.mtime, cached.size, cached.content_hash, mtime)
956                .then(|| cached.symbols.clone())
957        })
958    }
959
960    /// Return a cached symbol count when file metadata exactly matches the cache entry.
961    ///
962    /// This is the fast path for directory file-tree summaries: when mtime and
963    /// size are unchanged, callers can use the count without cloning symbols or
964    /// re-reading the file to verify a content hash.
965    pub fn symbol_count_if_metadata_matches(
966        &self,
967        path: &Path,
968        mtime: SystemTime,
969        size: u64,
970    ) -> Option<usize> {
971        self.entries.get(path).and_then(|cached| {
972            (cached.mtime == mtime && cached.size == size).then_some(cached.symbols.len())
973        })
974    }
975
976    /// Whether the cache has a still-valid entry for the given file mtime.
977    pub fn contains_path_with_mtime(&self, path: &Path, mtime: SystemTime) -> bool {
978        self.entries
979            .get(path)
980            .is_some_and(|cached| cached.mtime == mtime)
981    }
982
983    /// Load valid symbol entries from disk, dropping only entries whose source file changed.
984    pub fn load_from_disk(
985        &mut self,
986        storage_dir: &Path,
987        project_key: &str,
988        current_root: &Path,
989    ) -> usize {
990        debug_assert!(current_root.is_absolute());
991        let Some(cache) = symbol_cache_disk::read_from_disk(storage_dir, project_key) else {
992            return 0;
993        };
994
995        self.project_root = Some(current_root.to_path_buf());
996        self.entries.clear();
997        let mut loaded = 0usize;
998
999        for entry in cache.entries {
1000            let Some(path) =
1001                crate::search_index::cached_path_under_root(current_root, &entry.relative_path)
1002            else {
1003                continue;
1004            };
1005            let cached_freshness = FileFreshness {
1006                mtime: entry.mtime,
1007                size: entry.size,
1008                content_hash: entry.content_hash,
1009            };
1010            let mtime = match cache_freshness::verify_file(&path, &cached_freshness) {
1011                FreshnessVerdict::HotFresh => entry.mtime,
1012                FreshnessVerdict::ContentFresh { new_mtime, .. } => new_mtime,
1013                FreshnessVerdict::Stale | FreshnessVerdict::Deleted => continue,
1014            };
1015
1016            self.entries.insert(
1017                path,
1018                CachedSymbols {
1019                    mtime,
1020                    size: entry.size,
1021                    content_hash: entry.content_hash,
1022                    symbols: entry.symbols,
1023                },
1024            );
1025            loaded += 1;
1026        }
1027
1028        loaded
1029    }
1030
1031    /// Load valid symbol entries from disk only when the caller still belongs
1032    /// to the active cache generation.
1033    pub fn load_from_disk_for_generation(
1034        &mut self,
1035        generation: u64,
1036        storage_dir: &Path,
1037        project_key: &str,
1038        current_root: &Path,
1039    ) -> usize {
1040        if self.generation != generation {
1041            return 0;
1042        }
1043        self.load_from_disk(storage_dir, project_key, current_root)
1044    }
1045
1046    /// Invalidate cached symbols for a specific file.
1047    pub fn invalidate(&mut self, path: &Path) {
1048        self.entries.remove(path);
1049    }
1050
1051    /// Clear all entries and advance the generation to ignore stale background writers.
1052    pub fn reset(&mut self) -> u64 {
1053        self.entries.clear();
1054        self.project_root = None;
1055        self.generation = self.generation.wrapping_add(1);
1056        self.generation
1057    }
1058
1059    /// Current generation token.
1060    pub fn generation(&self) -> u64 {
1061        self.generation
1062    }
1063
1064    /// Whether the cache has an entry for a file.
1065    pub fn contains_key(&self, path: &Path) -> bool {
1066        self.entries.contains_key(path)
1067    }
1068
1069    /// Number of cached entries.
1070    pub fn len(&self) -> usize {
1071        self.entries.len()
1072    }
1073
1074    pub(crate) fn project_root(&self) -> Option<PathBuf> {
1075        self.project_root.clone()
1076    }
1077
1078    pub(crate) fn disk_entries(
1079        &self,
1080    ) -> Vec<(&PathBuf, SystemTime, u64, blake3::Hash, &Vec<Symbol>)> {
1081        // Persist EVERY parsed entry, including files that legitimately parse to
1082        // zero symbols (e.g. a TS file with only imports, a config-shaped file).
1083        // Previously these were filtered out, so they were never written to disk;
1084        // on the next spawn `load_from_disk` couldn't reload them, the prewarm
1085        // skip-check (`contains_path_with_mtime`) missed, and they were re-parsed
1086        // on every startup forever (the "N new" churn in issue #86). An empty
1087        // entry serializes to `symbols: []` — a few bytes — and makes the prewarm
1088        // skip it. It also makes the "persisted symbol cache: N files" count
1089        // accurate (was overstated by the filtered-out empties).
1090        self.entries
1091            .iter()
1092            .map(|(path, cached)| {
1093                (
1094                    path,
1095                    cached.mtime,
1096                    cached.size,
1097                    cached.content_hash,
1098                    &cached.symbols,
1099                )
1100            })
1101            .collect()
1102    }
1103}
1104
1105/// Core parsing engine. Handles language detection, parse tree caching,
1106/// symbol table caching, and query pattern execution via tree-sitter.
1107pub struct FileParser {
1108    cache: HashMap<PathBuf, CachedTree>,
1109    parsers: HashMap<LangId, Parser>,
1110    symbol_cache: SharedSymbolCache,
1111    symbol_cache_generation: Option<u64>,
1112}
1113
1114impl FileParser {
1115    /// Create a new `FileParser` with an empty parse cache.
1116    pub fn new() -> Self {
1117        Self::with_symbol_cache(Arc::new(RwLock::new(SymbolCache::new())))
1118    }
1119
1120    /// Create a new `FileParser` backed by a shared symbol cache.
1121    pub fn with_symbol_cache(symbol_cache: SharedSymbolCache) -> Self {
1122        Self::with_symbol_cache_generation(symbol_cache, None)
1123    }
1124
1125    /// Create a new `FileParser` backed by a shared symbol cache generation.
1126    pub fn with_symbol_cache_generation(
1127        symbol_cache: SharedSymbolCache,
1128        symbol_cache_generation: Option<u64>,
1129    ) -> Self {
1130        Self {
1131            cache: HashMap::new(),
1132            parsers: HashMap::new(),
1133            symbol_cache,
1134            symbol_cache_generation,
1135        }
1136    }
1137
1138    fn parser_for(&mut self, lang: LangId) -> Result<&mut Parser, AftError> {
1139        use std::collections::hash_map::Entry;
1140
1141        match self.parsers.entry(lang) {
1142            Entry::Occupied(entry) => Ok(entry.into_mut()),
1143            Entry::Vacant(entry) => {
1144                let grammar = grammar_for(lang);
1145                let mut parser = Parser::new();
1146                parser.set_language(&grammar).map_err(|e| {
1147                    crate::slog_error!("grammar init failed for {:?}: {}", lang, e);
1148                    AftError::ParseError {
1149                        message: format!("grammar init failed for {:?}: {}", lang, e),
1150                    }
1151                })?;
1152                Ok(entry.insert(parser))
1153            }
1154        }
1155    }
1156
1157    /// Number of entries in the shared symbol cache.
1158    pub fn symbol_cache_len(&self) -> usize {
1159        self.symbol_cache
1160            .read()
1161            .map(|cache| cache.len())
1162            .unwrap_or(0)
1163    }
1164
1165    /// Shared symbol cache backing this parser.
1166    pub fn symbol_cache(&self) -> SharedSymbolCache {
1167        Arc::clone(&self.symbol_cache)
1168    }
1169
1170    /// Parse a file, returning the tree and detected language. Uses cache if
1171    /// the file hasn't been modified since last parse.
1172    pub fn parse(&mut self, path: &Path) -> Result<(&Tree, LangId), AftError> {
1173        let lang = detect_language(path).ok_or_else(|| AftError::InvalidRequest {
1174            message: format!(
1175                "unsupported file extension: {}",
1176                path.extension()
1177                    .and_then(|e| e.to_str())
1178                    .unwrap_or("<none>")
1179            ),
1180        })?;
1181
1182        let canon = path.to_path_buf();
1183        let current_mtime = std::fs::metadata(path)
1184            .and_then(|m| m.modified())
1185            .map_err(|e| AftError::FileNotFound {
1186                path: format!("{}: {}", path.display(), e),
1187            })?;
1188
1189        // Check cache validity. Mtime alone is not enough: editors and tests can
1190        // restore timestamps after changing file contents, so small files fall
1191        // back to a Blake3 content hash when size does not prove staleness.
1192        let needs_reparse = match self.cache.get(&canon) {
1193            Some(cached) => !cached_file_is_fresh(
1194                path,
1195                cached.mtime,
1196                cached.size,
1197                cached.content_hash,
1198                current_mtime,
1199            ),
1200            None => true,
1201        };
1202
1203        if needs_reparse {
1204            let source = std::fs::read_to_string(path).map_err(|e| AftError::FileNotFound {
1205                path: format!("{}: {}", path.display(), e),
1206            })?;
1207
1208            let tree = self.parser_for(lang)?.parse(&source, None).ok_or_else(|| {
1209                crate::slog_error!("parse failed for {}", path.display());
1210                AftError::ParseError {
1211                    message: format!("tree-sitter parse returned None for {}", path.display()),
1212                }
1213            })?;
1214
1215            self.cache.insert(
1216                canon.clone(),
1217                CachedTree {
1218                    mtime: current_mtime,
1219                    size: source.len() as u64,
1220                    content_hash: content_hash_for_source(&source),
1221                    tree,
1222                },
1223            );
1224        }
1225
1226        let cached = self.cache.get(&canon).ok_or_else(|| AftError::ParseError {
1227            message: format!("parser cache missing entry for {}", path.display()),
1228        })?;
1229        Ok((&cached.tree, lang))
1230    }
1231
1232    /// Like [`FileParser::parse`] but returns an owned `Tree` clone.
1233    ///
1234    /// Useful when the caller needs to hold the tree while also calling
1235    /// other mutable methods on this parser.
1236    pub fn parse_cloned(&mut self, path: &Path) -> Result<(Tree, LangId), AftError> {
1237        let (tree, lang) = self.parse(path)?;
1238        Ok((tree.clone(), lang))
1239    }
1240
1241    /// Like [`FileParser::parse`] but reuses caller-provided file contents and
1242    /// metadata instead of reading + stat'ing the file again.
1243    ///
1244    /// `parse()` reads the file from disk to build the tree. A caller that has
1245    /// ALREADY read the source (e.g. [`FileParser::extract_symbols`], which
1246    /// reads it to hash + extract symbols) would otherwise pay a second
1247    /// `read_to_string` for the exact same bytes on every cold-cache file — a
1248    /// real cost when warming the symbol cache / call graph over a large repo.
1249    /// This variant takes the already-read `source` and the already-computed
1250    /// `current_mtime`/`size`/`content_hash` so the cold path does one read and
1251    /// one hash. Tree-cache freshness semantics are identical to `parse()`.
1252    fn parse_with_source(
1253        &mut self,
1254        path: &Path,
1255        source: &str,
1256        current_mtime: std::time::SystemTime,
1257        size: u64,
1258        content_hash: blake3::Hash,
1259    ) -> Result<(&Tree, LangId), AftError> {
1260        let lang = detect_language(path).ok_or_else(|| AftError::InvalidRequest {
1261            message: format!(
1262                "unsupported file extension: {}",
1263                path.extension()
1264                    .and_then(|e| e.to_str())
1265                    .unwrap_or("<none>")
1266            ),
1267        })?;
1268
1269        let canon = path.to_path_buf();
1270        let needs_reparse = match self.cache.get(&canon) {
1271            Some(cached) => !cached_file_is_fresh(
1272                path,
1273                cached.mtime,
1274                cached.size,
1275                cached.content_hash,
1276                current_mtime,
1277            ),
1278            None => true,
1279        };
1280
1281        if needs_reparse {
1282            let tree = self.parser_for(lang)?.parse(source, None).ok_or_else(|| {
1283                crate::slog_error!("parse failed for {}", path.display());
1284                AftError::ParseError {
1285                    message: format!("tree-sitter parse returned None for {}", path.display()),
1286                }
1287            })?;
1288
1289            self.cache.insert(
1290                canon.clone(),
1291                CachedTree {
1292                    mtime: current_mtime,
1293                    size,
1294                    content_hash,
1295                    tree,
1296                },
1297            );
1298        }
1299
1300        let cached = self.cache.get(&canon).ok_or_else(|| AftError::ParseError {
1301            message: format!("parser cache missing entry for {}", path.display()),
1302        })?;
1303        Ok((&cached.tree, lang))
1304    }
1305
1306    /// Extract symbols from a file using language-specific query patterns.
1307    /// Results are cached by `(path, mtime)` — subsequent calls for unchanged
1308    /// files return the cached symbol table without re-parsing.
1309    pub fn extract_symbols(&mut self, path: &Path) -> Result<Vec<Symbol>, AftError> {
1310        let canon = path.to_path_buf();
1311        let current_mtime = std::fs::metadata(path)
1312            .and_then(|m| m.modified())
1313            .map_err(|e| AftError::FileNotFound {
1314                path: format!("{}: {}", path.display(), e),
1315            })?;
1316
1317        // Return cached symbols if file hasn't changed.
1318        if let Some(symbols) = self
1319            .symbol_cache
1320            .read()
1321            .map_err(|_| AftError::ParseError {
1322                message: "symbol cache lock poisoned".to_string(),
1323            })?
1324            .get(&canon, current_mtime)
1325        {
1326            return Ok(symbols);
1327        }
1328
1329        let source = std::fs::read_to_string(path).map_err(|e| AftError::FileNotFound {
1330            path: format!("{}: {}", path.display(), e),
1331        })?;
1332        let size = source.len() as u64;
1333        let content_hash = content_hash_for_source(&source);
1334
1335        let symbols = {
1336            // Reuse the source we just read instead of letting parse() read the
1337            // same file a second time (cold-path double-read, council perf #5).
1338            let (tree, lang) =
1339                self.parse_with_source(path, &source, current_mtime, size, content_hash)?;
1340            extract_symbols_from_tree(&source, tree, lang)?
1341        };
1342
1343        // Cache the result
1344        let mut symbol_cache = self
1345            .symbol_cache
1346            .write()
1347            .map_err(|_| AftError::ParseError {
1348                message: "symbol cache lock poisoned".to_string(),
1349            })?;
1350        if let Some(generation) = self.symbol_cache_generation {
1351            symbol_cache.insert_for_generation(
1352                generation,
1353                canon,
1354                current_mtime,
1355                size,
1356                content_hash,
1357                symbols.clone(),
1358            );
1359        } else {
1360            symbol_cache.insert(canon, current_mtime, size, content_hash, symbols.clone());
1361        }
1362
1363        Ok(symbols)
1364    }
1365
1366    /// Invalidate cached symbols for a specific file (e.g., after an edit).
1367    pub fn invalidate_symbols(&mut self, path: &Path) {
1368        if let Ok(mut symbol_cache) = self.symbol_cache.write() {
1369            symbol_cache.invalidate(path);
1370        }
1371        self.cache.remove(path);
1372    }
1373}
1374
1375/// Extract symbols from an already-parsed tree without reparsing.
1376///
1377/// Callers that already have a `tree_sitter::Tree` (e.g. callgraph::build_file_data)
1378/// should use this instead of `list_symbols(path)` to avoid the redundant parse.
1379pub fn extract_symbols_from_tree(
1380    source: &str,
1381    tree: &Tree,
1382    lang: LangId,
1383) -> Result<Vec<Symbol>, AftError> {
1384    let root = tree.root_node();
1385
1386    if lang == LangId::Html {
1387        return extract_html_symbols(source, &root);
1388    }
1389    if lang == LangId::Markdown {
1390        return extract_md_symbols(source, &root);
1391    }
1392    if lang == LangId::Vue {
1393        return extract_vue_symbols(source, &root);
1394    }
1395    if lang == LangId::Json {
1396        return extract_json_symbols(source, &root);
1397    }
1398    if lang == LangId::Yaml {
1399        return extract_yaml_symbols(source, &root);
1400    }
1401
1402    let query = cached_query_for(lang)?.ok_or_else(|| AftError::InvalidRequest {
1403        message: format!("no query patterns implemented for {:?} yet", lang),
1404    })?;
1405
1406    match lang {
1407        LangId::TypeScript | LangId::Tsx => extract_ts_symbols(source, &root, query),
1408        LangId::JavaScript => extract_js_symbols(source, &root, query),
1409        LangId::Python => extract_py_symbols(source, &root, query),
1410        LangId::Rust => extract_rs_symbols(source, &root, query),
1411        LangId::Go => extract_go_symbols(source, &root, query),
1412        LangId::C => extract_c_symbols(source, &root, query),
1413        LangId::Cpp => extract_cpp_symbols(source, &root, query),
1414        LangId::Zig => extract_zig_symbols(source, &root, query),
1415        LangId::CSharp => extract_csharp_symbols(source, &root, query),
1416        LangId::Bash => extract_bash_symbols(source, &root, query),
1417        LangId::Solidity => extract_solidity_symbols(source, &root, query),
1418        LangId::Scss => extract_scss_symbols(source, &root, query),
1419        LangId::Scala => extract_scala_symbols(source, &root, query),
1420        LangId::Java => extract_java_symbols(source, &root, query),
1421        LangId::Ruby => extract_ruby_symbols(source, &root, query),
1422        LangId::Kotlin => extract_kotlin_symbols(source, &root, query),
1423        LangId::Swift => extract_swift_symbols(source, &root, query),
1424        LangId::Php => extract_php_symbols(source, &root, query),
1425        LangId::Lua => extract_lua_symbols(source, &root, query),
1426        LangId::Perl => extract_perl_symbols(source, &root, query),
1427        LangId::Pascal => extract_pascal_symbols(source, &root, query),
1428        LangId::Html | LangId::Markdown | LangId::Vue | LangId::Json | LangId::Yaml => {
1429            unreachable!("handled before query lookup")
1430        }
1431    }
1432}
1433
1434/// Build a Range from a tree-sitter Node.
1435pub(crate) fn node_range(node: &Node) -> Range {
1436    let start = node.start_position();
1437    let end = node.end_position();
1438    Range {
1439        start_line: start.row as u32,
1440        start_col: start.column as u32,
1441        end_line: end.row as u32,
1442        end_col: end.column as u32,
1443    }
1444}
1445
1446/// Build a Range from a tree-sitter Node, expanding upward to include
1447/// preceding attributes, decorators, and doc comments that belong to the symbol.
1448///
1449/// This ensures that when agents edit/replace a symbol, they get the full
1450/// declaration including `#[test]`, `#[derive(...)]`, `/// doc`, `@decorator`, etc.
1451pub(crate) fn node_range_with_decorators(node: &Node, source: &str, lang: LangId) -> Range {
1452    if matches!(lang, LangId::Python) {
1453        if let Some(parent) = node.parent() {
1454            if parent.kind() == "decorated_definition" {
1455                return node_range(&parent);
1456            }
1457        }
1458    }
1459
1460    // TypeScript / JavaScript / TSX: `export function foo() {}` parses as
1461    // `export_statement > function_declaration`. The function/class/etc.
1462    // node alone starts AFTER the `export ` keyword, so symbol replace would
1463    // produce a syntactically-broken `export export function foo() {}` if the
1464    // agent's replacement content includes its own `export` (it almost always
1465    // does, because they're replacing the *declaration*). Walk up to the
1466    // export_statement so the range covers `export ...`/`export default ...`.
1467    if matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
1468        if let Some(parent) = node.parent() {
1469            if parent.kind() == "export_statement" {
1470                return node_range_with_decorators_inner(&parent, source, lang);
1471            }
1472        }
1473    }
1474
1475    node_range_with_decorators_inner(node, source, lang)
1476}
1477
1478/// Inner walk that handles preceding-sibling expansion (decorators, doc comments).
1479/// Split from `node_range_with_decorators` so the export-statement parent path can
1480/// recurse without re-checking the export wrapper.
1481fn node_range_with_decorators_inner(node: &Node, source: &str, lang: LangId) -> Range {
1482    let mut range = node_range(node);
1483
1484    let mut current = *node;
1485    while let Some(prev) = current.prev_sibling() {
1486        let kind = prev.kind();
1487        let should_include = match lang {
1488            LangId::Rust => {
1489                // Include #[...] attributes
1490                kind == "attribute_item"
1491                    // Include /// doc comments (but not regular // comments)
1492                    || (kind == "line_comment"
1493                        && node_text(source, &prev).starts_with("///"))
1494                    // Include /** ... */ doc comments
1495                    || (kind == "block_comment"
1496                        && node_text(source, &prev).starts_with("/**"))
1497            }
1498            LangId::TypeScript | LangId::Tsx | LangId::JavaScript => {
1499                // Include @decorator
1500                kind == "decorator"
1501                    // Include adjacent /** JSDoc */ comments, but do not let a
1502                    // blank line attach file-level docs to the following symbol.
1503                    || (kind == "comment"
1504                        && node_text(source, &prev).starts_with("/**")
1505                        && is_adjacent_line(&prev, &current, source))
1506            }
1507            LangId::Go
1508            | LangId::C
1509            | LangId::Cpp
1510            | LangId::Zig
1511            | LangId::CSharp
1512            | LangId::Bash
1513            | LangId::Pascal => {
1514                // Include doc comments only if immediately above (no blank line gap)
1515                kind == "comment" && is_adjacent_line(&prev, &current, source)
1516            }
1517            LangId::Solidity
1518            | LangId::Scss
1519            | LangId::Scala
1520            | LangId::Java
1521            | LangId::Kotlin
1522            | LangId::Swift
1523            | LangId::Php => {
1524                // Include `///` doc comments and `/** */` doc blocks if immediately above
1525                let text = node_text(source, &prev);
1526                (kind == "comment" || kind == "line_comment" || kind == "block_comment")
1527                    && (text.starts_with("///") || text.starts_with("/**"))
1528                    && is_adjacent_line(&prev, &current, source)
1529            }
1530            LangId::Ruby | LangId::Lua => {
1531                // Include adjacent `#`/`---` style comments used as documentation.
1532                let text = node_text(source, &prev);
1533                kind == "comment"
1534                    && (text.starts_with('#') || text.starts_with("---"))
1535                    && is_adjacent_line(&prev, &current, source)
1536            }
1537            LangId::Perl => false,
1538            LangId::Python => {
1539                // Decorators are handled by decorated_definition capture
1540                false
1541            }
1542            LangId::Html | LangId::Markdown | LangId::Vue | LangId::Json | LangId::Yaml => false,
1543        };
1544
1545        if should_include {
1546            range.start_line = prev.start_position().row as u32;
1547            range.start_col = prev.start_position().column as u32;
1548            current = prev;
1549        } else {
1550            break;
1551        }
1552    }
1553
1554    range
1555}
1556
1557/// Check if two nodes are on adjacent lines (no blank line between them).
1558fn is_adjacent_line(upper: &Node, lower: &Node, source: &str) -> bool {
1559    let upper_end = upper.end_position().row;
1560    let lower_start = lower.start_position().row;
1561
1562    if lower_start == 0 || lower_start <= upper_end {
1563        return true;
1564    }
1565
1566    // Check that there's no blank line between them
1567    let lines: Vec<&str> = source.lines().collect();
1568    for row in (upper_end + 1)..lower_start {
1569        if row < lines.len() && lines[row].trim().is_empty() {
1570            return false;
1571        }
1572    }
1573    true
1574}
1575
1576/// Extract the text of a node from source.
1577pub(crate) fn node_text<'a>(source: &'a str, node: &Node) -> &'a str {
1578    &source[node.byte_range()]
1579}
1580
1581fn lexical_declaration_has_function_value(node: &Node) -> bool {
1582    let mut cursor = node.walk();
1583    if !cursor.goto_first_child() {
1584        return false;
1585    }
1586
1587    loop {
1588        let child = cursor.node();
1589        if matches!(
1590            child.kind(),
1591            "arrow_function" | "function_expression" | "generator_function"
1592        ) {
1593            return true;
1594        }
1595
1596        if lexical_declaration_has_function_value(&child) {
1597            return true;
1598        }
1599
1600        if !cursor.goto_next_sibling() {
1601            break;
1602        }
1603    }
1604
1605    false
1606}
1607
1608fn variable_declarator_has_function_value(node: &Node) -> bool {
1609    node.child_by_field_name("value").is_some_and(|value| {
1610        matches!(
1611            value.kind(),
1612            "arrow_function" | "function_expression" | "generator_function"
1613        )
1614    })
1615}
1616
1617/// Collect byte ranges of all export_statement nodes from query matches.
1618fn collect_export_ranges(source: &str, root: &Node, query: &Query) -> Vec<std::ops::Range<usize>> {
1619    let export_idx = query
1620        .capture_names()
1621        .iter()
1622        .position(|n| *n == "export.stmt");
1623    let export_idx = match export_idx {
1624        Some(i) => i as u32,
1625        None => return vec![],
1626    };
1627
1628    let mut cursor = QueryCursor::new();
1629    let mut ranges = Vec::new();
1630    let mut matches = cursor.matches(query, *root, source.as_bytes());
1631
1632    while let Some(m) = {
1633        matches.advance();
1634        matches.get()
1635    } {
1636        for cap in m.captures {
1637            if cap.index == export_idx {
1638                ranges.push(cap.node.byte_range());
1639            }
1640        }
1641    }
1642    ranges
1643}
1644
1645/// Check if a node's byte range is contained within any export statement.
1646fn is_exported(node: &Node, export_ranges: &[std::ops::Range<usize>]) -> bool {
1647    let r = node.byte_range();
1648    export_ranges
1649        .iter()
1650        .any(|er| er.start <= r.start && r.end <= er.end)
1651}
1652
1653fn collect_exported_symbol_names(source: &str, root: &Node) -> HashSet<String> {
1654    let mut exported = HashSet::new();
1655    collect_exported_symbol_names_inner(source, root, &mut exported);
1656    exported
1657}
1658
1659fn collect_exported_symbol_names_inner(source: &str, node: &Node, exported: &mut HashSet<String>) {
1660    if node.kind() == "export_statement" {
1661        collect_names_from_export_statement(source, node, exported);
1662    }
1663
1664    let mut cursor = node.walk();
1665    if !cursor.goto_first_child() {
1666        return;
1667    }
1668
1669    loop {
1670        let child = cursor.node();
1671        collect_exported_symbol_names_inner(source, &child, exported);
1672        if !cursor.goto_next_sibling() {
1673            break;
1674        }
1675    }
1676}
1677
1678fn collect_names_from_export_statement(source: &str, node: &Node, exported: &mut HashSet<String>) {
1679    let mut cursor = node.walk();
1680    if !cursor.goto_first_child() {
1681        return;
1682    }
1683
1684    let mut saw_default = false;
1685    loop {
1686        let child = cursor.node();
1687        match child.kind() {
1688            "default" => saw_default = true,
1689            "export_clause" => collect_names_from_export_clause(source, &child, exported),
1690            "identifier" | "type_identifier" | "property_identifier" if saw_default => {
1691                exported.insert(node_text(source, &child).to_string());
1692                return;
1693            }
1694            _ => {}
1695        }
1696        if !cursor.goto_next_sibling() {
1697            break;
1698        }
1699    }
1700}
1701
1702fn collect_names_from_export_clause(source: &str, node: &Node, exported: &mut HashSet<String>) {
1703    let mut cursor = node.walk();
1704    if !cursor.goto_first_child() {
1705        return;
1706    }
1707
1708    loop {
1709        let child = cursor.node();
1710        if child.kind() == "export_specifier" {
1711            if let Some(exported_name) = last_identifier_text(source, &child) {
1712                exported.insert(exported_name);
1713            }
1714        }
1715        if !cursor.goto_next_sibling() {
1716            break;
1717        }
1718    }
1719}
1720
1721fn last_identifier_text(source: &str, node: &Node) -> Option<String> {
1722    let mut cursor = node.walk();
1723    if !cursor.goto_first_child() {
1724        return None;
1725    }
1726
1727    let mut last = None;
1728    loop {
1729        let child = cursor.node();
1730        if matches!(
1731            child.kind(),
1732            "identifier"
1733                | "type_identifier"
1734                | "property_identifier"
1735                | "shorthand_property_identifier"
1736        ) {
1737            last = Some(node_text(source, &child).to_string());
1738        }
1739        if !cursor.goto_next_sibling() {
1740            break;
1741        }
1742    }
1743    last
1744}
1745
1746fn mark_named_exports(symbols: &mut [Symbol], exported_names: &HashSet<String>) {
1747    for symbol in symbols {
1748        if symbol.scope_chain.is_empty()
1749            && symbol.parent.is_none()
1750            && exported_names.contains(&symbol.name)
1751        {
1752            symbol.exported = true;
1753        }
1754    }
1755}
1756
1757/// Extract the first line of a node as its signature.
1758fn extract_signature(source: &str, node: &Node) -> String {
1759    let text = node_text(source, node);
1760    let first_line = text.lines().next().unwrap_or(text);
1761    // Trim trailing opening brace if present
1762    let trimmed = first_line.trim_end();
1763    let trimmed = trimmed.strip_suffix('{').unwrap_or(trimmed).trim_end();
1764    trimmed.to_string()
1765}
1766
1767fn push_default_export_symbol(
1768    symbols: &mut Vec<Symbol>,
1769    source: &str,
1770    lang: LangId,
1771    body_node: Node,
1772    def_node: Node,
1773) {
1774    let kind = if body_node.kind() == "class" {
1775        SymbolKind::Class
1776    } else {
1777        SymbolKind::Function
1778    };
1779
1780    symbols.push(Symbol {
1781        name: "default".to_string(),
1782        kind,
1783        range: node_range_with_decorators(&def_node, source, lang),
1784        signature: Some(extract_signature(source, &def_node)),
1785        scope_chain: vec![],
1786        exported: true,
1787        parent: None,
1788    });
1789}
1790
1791/// Extract symbols from TypeScript / TSX source.
1792fn extract_ts_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1793    let lang = LangId::TypeScript;
1794    let capture_names = query.capture_names();
1795
1796    let export_ranges = collect_export_ranges(source, root, query);
1797    let exported_names = collect_exported_symbol_names(source, root);
1798
1799    let mut symbols = Vec::new();
1800    let mut cursor = QueryCursor::new();
1801    let mut matches = cursor.matches(query, *root, source.as_bytes());
1802
1803    while let Some(m) = {
1804        matches.advance();
1805        matches.get()
1806    } {
1807        // Determine what kind of match this is by looking at capture names
1808        let mut fn_name_node = None;
1809        let mut fn_def_node = None;
1810        let mut arrow_name_node = None;
1811        let mut arrow_def_node = None;
1812        let mut arrow_decl_node = None;
1813        let mut class_name_node = None;
1814        let mut class_def_node = None;
1815        let mut method_class_name_node = None;
1816        let mut method_name_node = None;
1817        let mut method_def_node = None;
1818        let mut interface_name_node = None;
1819        let mut interface_def_node = None;
1820        let mut enum_name_node = None;
1821        let mut enum_def_node = None;
1822        let mut type_alias_name_node = None;
1823        let mut type_alias_def_node = None;
1824        let mut var_name_node = None;
1825        let mut var_def_node = None;
1826        let mut var_decl_node = None;
1827        let mut default_body_node = None;
1828        let mut default_def_node = None;
1829
1830        for cap in m.captures {
1831            let Some(&name) = capture_names.get(cap.index as usize) else {
1832                continue;
1833            };
1834            match name {
1835                "fn.name" => fn_name_node = Some(cap.node),
1836                "fn.def" => fn_def_node = Some(cap.node),
1837                "arrow.name" => arrow_name_node = Some(cap.node),
1838                "arrow.def" => arrow_def_node = Some(cap.node),
1839                "arrow.decl" => arrow_decl_node = Some(cap.node),
1840                "class.name" => class_name_node = Some(cap.node),
1841                "class.def" => class_def_node = Some(cap.node),
1842                "method.class_name" => method_class_name_node = Some(cap.node),
1843                "method.name" => method_name_node = Some(cap.node),
1844                "method.def" => method_def_node = Some(cap.node),
1845                "interface.name" => interface_name_node = Some(cap.node),
1846                "interface.def" => interface_def_node = Some(cap.node),
1847                "enum.name" => enum_name_node = Some(cap.node),
1848                "enum.def" => enum_def_node = Some(cap.node),
1849                "type_alias.name" => type_alias_name_node = Some(cap.node),
1850                "type_alias.def" => type_alias_def_node = Some(cap.node),
1851                "var.name" => var_name_node = Some(cap.node),
1852                "var.def" => var_def_node = Some(cap.node),
1853                "var.decl" => var_decl_node = Some(cap.node),
1854                "default.body" => default_body_node = Some(cap.node),
1855                "default.def" => default_def_node = Some(cap.node),
1856                _ => {}
1857            }
1858        }
1859
1860        // Function declaration
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        // Arrow/function expression declarator
1874        if let (Some(name_node), Some(def_node)) = (arrow_name_node, arrow_def_node) {
1875            let range_node = arrow_decl_node.unwrap_or(def_node);
1876            symbols.push(Symbol {
1877                name: node_text(source, &name_node).to_string(),
1878                kind: SymbolKind::Function,
1879                range: node_range_with_decorators(&range_node, source, lang),
1880                signature: Some(extract_signature(source, &def_node)),
1881                scope_chain: vec![],
1882                exported: is_exported(&def_node, &export_ranges),
1883                parent: None,
1884            });
1885        }
1886
1887        // Anonymous/default function or class expression
1888        if let (Some(body_node), Some(def_node)) = (default_body_node, default_def_node) {
1889            push_default_export_symbol(&mut symbols, source, lang, body_node, def_node);
1890        }
1891
1892        // Class declaration
1893        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
1894            symbols.push(Symbol {
1895                name: node_text(source, &name_node).to_string(),
1896                kind: SymbolKind::Class,
1897                range: node_range_with_decorators(&def_node, source, lang),
1898                signature: Some(extract_signature(source, &def_node)),
1899                scope_chain: vec![],
1900                exported: is_exported(&def_node, &export_ranges),
1901                parent: None,
1902            });
1903        }
1904
1905        // Method definition
1906        if let (Some(class_name_node), Some(name_node), Some(def_node)) =
1907            (method_class_name_node, method_name_node, method_def_node)
1908        {
1909            let class_name = node_text(source, &class_name_node).to_string();
1910            symbols.push(Symbol {
1911                name: node_text(source, &name_node).to_string(),
1912                kind: SymbolKind::Method,
1913                range: node_range_with_decorators(&def_node, source, lang),
1914                signature: Some(extract_signature(source, &def_node)),
1915                scope_chain: vec![class_name.clone()],
1916                exported: false, // methods inherit export from class
1917                parent: Some(class_name),
1918            });
1919        }
1920
1921        // Interface declaration
1922        if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
1923            symbols.push(Symbol {
1924                name: node_text(source, &name_node).to_string(),
1925                kind: SymbolKind::Interface,
1926                range: node_range_with_decorators(&def_node, source, lang),
1927                signature: Some(extract_signature(source, &def_node)),
1928                scope_chain: vec![],
1929                exported: is_exported(&def_node, &export_ranges),
1930                parent: None,
1931            });
1932        }
1933
1934        // Enum declaration
1935        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
1936            symbols.push(Symbol {
1937                name: node_text(source, &name_node).to_string(),
1938                kind: SymbolKind::Enum,
1939                range: node_range_with_decorators(&def_node, source, lang),
1940                signature: Some(extract_signature(source, &def_node)),
1941                scope_chain: vec![],
1942                exported: is_exported(&def_node, &export_ranges),
1943                parent: None,
1944            });
1945        }
1946
1947        // Type alias
1948        if let (Some(name_node), Some(def_node)) = (type_alias_name_node, type_alias_def_node) {
1949            symbols.push(Symbol {
1950                name: node_text(source, &name_node).to_string(),
1951                kind: SymbolKind::TypeAlias,
1952                range: node_range_with_decorators(&def_node, source, lang),
1953                signature: Some(extract_signature(source, &def_node)),
1954                scope_chain: vec![],
1955                exported: is_exported(&def_node, &export_ranges),
1956                parent: None,
1957            });
1958        }
1959
1960        // Top-level const/let variable declaration (not arrow functions — those are handled above)
1961        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
1962            // Only include module-scope variables (parent is program/export_statement, not inside a function)
1963            let is_top_level = def_node
1964                .parent()
1965                .map(|p| p.kind() == "program" || p.kind() == "export_statement")
1966                .unwrap_or(false);
1967            let range_node = var_decl_node.unwrap_or(def_node);
1968            let is_function_like = if range_node.kind() == "variable_declarator" {
1969                variable_declarator_has_function_value(&range_node)
1970            } else {
1971                lexical_declaration_has_function_value(&def_node)
1972            };
1973            let name = node_text(source, &name_node).to_string();
1974            let already_captured = symbols.iter().any(|s| s.name == name);
1975            if is_top_level && !is_function_like && !already_captured {
1976                symbols.push(Symbol {
1977                    name,
1978                    kind: SymbolKind::Variable,
1979                    range: node_range_with_decorators(&range_node, source, lang),
1980                    signature: Some(extract_signature(source, &def_node)),
1981                    scope_chain: vec![],
1982                    exported: is_exported(&def_node, &export_ranges),
1983                    parent: None,
1984                });
1985            }
1986        }
1987    }
1988
1989    mark_named_exports(&mut symbols, &exported_names);
1990
1991    // Deduplicate: methods can appear as both class and method captures
1992    dedup_symbols(&mut symbols);
1993    Ok(symbols)
1994}
1995
1996/// Extract symbols from JavaScript source.
1997fn extract_js_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1998    let lang = LangId::JavaScript;
1999    let capture_names = query.capture_names();
2000
2001    let export_ranges = collect_export_ranges(source, root, query);
2002    let exported_names = collect_exported_symbol_names(source, root);
2003
2004    let mut symbols = Vec::new();
2005    let mut cursor = QueryCursor::new();
2006    let mut matches = cursor.matches(query, *root, source.as_bytes());
2007
2008    while let Some(m) = {
2009        matches.advance();
2010        matches.get()
2011    } {
2012        let mut fn_name_node = None;
2013        let mut fn_def_node = None;
2014        let mut arrow_name_node = None;
2015        let mut arrow_def_node = None;
2016        let mut arrow_decl_node = None;
2017        let mut class_name_node = None;
2018        let mut class_def_node = None;
2019        let mut method_class_name_node = None;
2020        let mut method_name_node = None;
2021        let mut method_def_node = None;
2022        let mut default_body_node = None;
2023        let mut default_def_node = None;
2024        let mut var_name_node = None;
2025        let mut var_def_node = None;
2026        let mut var_decl_node = None;
2027
2028        for cap in m.captures {
2029            let Some(&name) = capture_names.get(cap.index as usize) else {
2030                continue;
2031            };
2032            match name {
2033                "fn.name" => fn_name_node = Some(cap.node),
2034                "fn.def" => fn_def_node = Some(cap.node),
2035                "arrow.name" => arrow_name_node = Some(cap.node),
2036                "arrow.def" => arrow_def_node = Some(cap.node),
2037                "arrow.decl" => arrow_decl_node = Some(cap.node),
2038                "class.name" => class_name_node = Some(cap.node),
2039                "class.def" => class_def_node = Some(cap.node),
2040                "method.class_name" => method_class_name_node = Some(cap.node),
2041                "method.name" => method_name_node = Some(cap.node),
2042                "method.def" => method_def_node = Some(cap.node),
2043                "default.body" => default_body_node = Some(cap.node),
2044                "default.def" => default_def_node = Some(cap.node),
2045                "var.name" => var_name_node = Some(cap.node),
2046                "var.def" => var_def_node = Some(cap.node),
2047                "var.decl" => var_decl_node = Some(cap.node),
2048                _ => {}
2049            }
2050        }
2051
2052        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2053            symbols.push(Symbol {
2054                name: node_text(source, &name_node).to_string(),
2055                kind: SymbolKind::Function,
2056                range: node_range_with_decorators(&def_node, source, lang),
2057                signature: Some(extract_signature(source, &def_node)),
2058                scope_chain: vec![],
2059                exported: is_exported(&def_node, &export_ranges),
2060                parent: None,
2061            });
2062        }
2063
2064        if let (Some(name_node), Some(def_node)) = (arrow_name_node, arrow_def_node) {
2065            let range_node = arrow_decl_node.unwrap_or(def_node);
2066            symbols.push(Symbol {
2067                name: node_text(source, &name_node).to_string(),
2068                kind: SymbolKind::Function,
2069                range: node_range_with_decorators(&range_node, source, lang),
2070                signature: Some(extract_signature(source, &def_node)),
2071                scope_chain: vec![],
2072                exported: is_exported(&def_node, &export_ranges),
2073                parent: None,
2074            });
2075        }
2076
2077        if let (Some(body_node), Some(def_node)) = (default_body_node, default_def_node) {
2078            push_default_export_symbol(&mut symbols, source, lang, body_node, def_node);
2079        }
2080
2081        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
2082            symbols.push(Symbol {
2083                name: node_text(source, &name_node).to_string(),
2084                kind: SymbolKind::Class,
2085                range: node_range_with_decorators(&def_node, source, lang),
2086                signature: Some(extract_signature(source, &def_node)),
2087                scope_chain: vec![],
2088                exported: is_exported(&def_node, &export_ranges),
2089                parent: None,
2090            });
2091        }
2092
2093        if let (Some(class_name_node), Some(name_node), Some(def_node)) =
2094            (method_class_name_node, method_name_node, method_def_node)
2095        {
2096            let class_name = node_text(source, &class_name_node).to_string();
2097            symbols.push(Symbol {
2098                name: node_text(source, &name_node).to_string(),
2099                kind: SymbolKind::Method,
2100                range: node_range_with_decorators(&def_node, source, lang),
2101                signature: Some(extract_signature(source, &def_node)),
2102                scope_chain: vec![class_name.clone()],
2103                exported: false,
2104                parent: Some(class_name),
2105            });
2106        }
2107
2108        // Top-level const/let/var declarations. JS_QUERY captures these the same
2109        // way TS does, but the JS extractor previously dropped them (they fell
2110        // into the `_ => {}` arm), so e.g. `export const VERSION = "1.0"` in a
2111        // .js file produced no symbol for outline/dead_code/callgraph. Mirror the
2112        // TS extractor: module-scope, non-function-valued, not already captured.
2113        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
2114            let is_top_level = def_node
2115                .parent()
2116                .map(|p| p.kind() == "program" || p.kind() == "export_statement")
2117                .unwrap_or(false);
2118            let range_node = var_decl_node.unwrap_or(def_node);
2119            let is_function_like = if range_node.kind() == "variable_declarator" {
2120                variable_declarator_has_function_value(&range_node)
2121            } else {
2122                lexical_declaration_has_function_value(&def_node)
2123            };
2124            let name = node_text(source, &name_node).to_string();
2125            let already_captured = symbols.iter().any(|s| s.name == name);
2126            if is_top_level && !is_function_like && !already_captured {
2127                symbols.push(Symbol {
2128                    name,
2129                    kind: SymbolKind::Variable,
2130                    range: node_range_with_decorators(&range_node, source, lang),
2131                    signature: Some(extract_signature(source, &def_node)),
2132                    scope_chain: vec![],
2133                    exported: is_exported(&def_node, &export_ranges),
2134                    parent: None,
2135                });
2136            }
2137        }
2138    }
2139
2140    mark_named_exports(&mut symbols, &exported_names);
2141    dedup_symbols(&mut symbols);
2142    Ok(symbols)
2143}
2144
2145/// Walk parent nodes to build a scope chain for Python symbols.
2146/// A function inside `class_definition > block` gets the class name in its scope.
2147fn py_scope_chain(node: &Node, source: &str) -> Vec<String> {
2148    let mut chain = Vec::new();
2149    let mut current = node.parent();
2150    while let Some(parent) = current {
2151        if parent.kind() == "class_definition" {
2152            if let Some(name_node) = parent.child_by_field_name("name") {
2153                chain.push(node_text(source, &name_node).to_string());
2154            }
2155        }
2156        current = parent.parent();
2157    }
2158    chain.reverse();
2159    chain
2160}
2161
2162/// Extract symbols from Python source.
2163fn extract_py_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2164    let lang = LangId::Python;
2165    let capture_names = query.capture_names();
2166
2167    let mut symbols = Vec::new();
2168    let mut cursor = QueryCursor::new();
2169    let mut matches = cursor.matches(query, *root, source.as_bytes());
2170
2171    // Track decorated definitions to avoid double-counting
2172    let mut decorated_fn_lines = std::collections::HashSet::new();
2173
2174    // First pass: collect decorated definition info
2175    {
2176        let mut cursor2 = QueryCursor::new();
2177        let mut matches2 = cursor2.matches(query, *root, source.as_bytes());
2178        while let Some(m) = {
2179            matches2.advance();
2180            matches2.get()
2181        } {
2182            let mut dec_def_node = None;
2183            let mut dec_decorator_node = None;
2184
2185            for cap in m.captures {
2186                let Some(&name) = capture_names.get(cap.index as usize) else {
2187                    continue;
2188                };
2189                match name {
2190                    "dec.def" => dec_def_node = Some(cap.node),
2191                    "dec.decorator" => dec_decorator_node = Some(cap.node),
2192                    _ => {}
2193                }
2194            }
2195
2196            if let (Some(def_node), Some(_dec_node)) = (dec_def_node, dec_decorator_node) {
2197                // Find the inner function_definition or class_definition
2198                let mut child_cursor = def_node.walk();
2199                if child_cursor.goto_first_child() {
2200                    loop {
2201                        let child = child_cursor.node();
2202                        if child.kind() == "function_definition"
2203                            || child.kind() == "class_definition"
2204                        {
2205                            decorated_fn_lines.insert(child.start_position().row);
2206                        }
2207                        if !child_cursor.goto_next_sibling() {
2208                            break;
2209                        }
2210                    }
2211                }
2212            }
2213        }
2214    }
2215
2216    while let Some(m) = {
2217        matches.advance();
2218        matches.get()
2219    } {
2220        let mut fn_name_node = None;
2221        let mut fn_def_node = None;
2222        let mut class_name_node = None;
2223        let mut class_def_node = None;
2224
2225        for cap in m.captures {
2226            let Some(&name) = capture_names.get(cap.index as usize) else {
2227                continue;
2228            };
2229            match name {
2230                "fn.name" => fn_name_node = Some(cap.node),
2231                "fn.def" => fn_def_node = Some(cap.node),
2232                "class.name" => class_name_node = Some(cap.node),
2233                "class.def" => class_def_node = Some(cap.node),
2234                _ => {}
2235            }
2236        }
2237
2238        // Function definition
2239        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2240            let scope = py_scope_chain(&def_node, source);
2241            let is_method = !scope.is_empty();
2242            let name = node_text(source, &name_node).to_string();
2243            // Skip __init__ and other dunders as separate symbols — they're methods
2244            let kind = if is_method {
2245                SymbolKind::Method
2246            } else {
2247                SymbolKind::Function
2248            };
2249
2250            // Build signature — include decorator if this is a decorated function
2251            let sig = if decorated_fn_lines.contains(&def_node.start_position().row) {
2252                // Find the decorated_definition parent to get decorator text
2253                let mut sig_parts = Vec::new();
2254                let mut parent = def_node.parent();
2255                while let Some(p) = parent {
2256                    if p.kind() == "decorated_definition" {
2257                        // Get decorator lines
2258                        let mut dc = p.walk();
2259                        if dc.goto_first_child() {
2260                            loop {
2261                                if dc.node().kind() == "decorator" {
2262                                    sig_parts.push(node_text(source, &dc.node()).to_string());
2263                                }
2264                                if !dc.goto_next_sibling() {
2265                                    break;
2266                                }
2267                            }
2268                        }
2269                        break;
2270                    }
2271                    parent = p.parent();
2272                }
2273                sig_parts.push(extract_signature(source, &def_node));
2274                Some(sig_parts.join("\n"))
2275            } else {
2276                Some(extract_signature(source, &def_node))
2277            };
2278
2279            symbols.push(Symbol {
2280                name,
2281                kind,
2282                range: node_range_with_decorators(&def_node, source, lang),
2283                signature: sig,
2284                scope_chain: scope.clone(),
2285                exported: false, // Python has no export concept
2286                parent: scope.last().cloned(),
2287            });
2288        }
2289
2290        // Class definition
2291        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
2292            let scope = py_scope_chain(&def_node, source);
2293
2294            // Build signature — include decorator if decorated
2295            let sig = if decorated_fn_lines.contains(&def_node.start_position().row) {
2296                let mut sig_parts = Vec::new();
2297                let mut parent = def_node.parent();
2298                while let Some(p) = parent {
2299                    if p.kind() == "decorated_definition" {
2300                        let mut dc = p.walk();
2301                        if dc.goto_first_child() {
2302                            loop {
2303                                if dc.node().kind() == "decorator" {
2304                                    sig_parts.push(node_text(source, &dc.node()).to_string());
2305                                }
2306                                if !dc.goto_next_sibling() {
2307                                    break;
2308                                }
2309                            }
2310                        }
2311                        break;
2312                    }
2313                    parent = p.parent();
2314                }
2315                sig_parts.push(extract_signature(source, &def_node));
2316                Some(sig_parts.join("\n"))
2317            } else {
2318                Some(extract_signature(source, &def_node))
2319            };
2320
2321            symbols.push(Symbol {
2322                name: node_text(source, &name_node).to_string(),
2323                kind: SymbolKind::Class,
2324                range: node_range_with_decorators(&def_node, source, lang),
2325                signature: sig,
2326                scope_chain: scope.clone(),
2327                exported: false,
2328                parent: scope.last().cloned(),
2329            });
2330        }
2331    }
2332
2333    dedup_symbols(&mut symbols);
2334    Ok(symbols)
2335}
2336
2337fn rust_mod_scope_chain(node: &Node, source: &str) -> Vec<String> {
2338    let mut scopes = Vec::new();
2339    let mut current = node.parent();
2340
2341    while let Some(parent) = current {
2342        if parent.kind() == "mod_item" {
2343            if let Some(name_node) = parent.child_by_field_name("name") {
2344                scopes.push(node_text(source, &name_node).to_string());
2345            }
2346        }
2347        current = parent.parent();
2348    }
2349
2350    scopes.reverse();
2351    scopes
2352}
2353
2354/// Extract symbols from Rust source.
2355/// Handles: free functions, struct, enum, trait (as Interface), impl methods with scope chains.
2356fn extract_rs_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2357    let lang = LangId::Rust;
2358    let capture_names = query.capture_names();
2359
2360    // Collect all visibility_modifier byte ranges first
2361    let mut vis_ranges: Vec<std::ops::Range<usize>> = Vec::new();
2362    {
2363        let vis_idx = capture_names.iter().position(|n| *n == "vis.mod");
2364        if let Some(idx) = vis_idx {
2365            let idx = idx as u32;
2366            let mut cursor = QueryCursor::new();
2367            let mut matches = cursor.matches(query, *root, source.as_bytes());
2368            while let Some(m) = {
2369                matches.advance();
2370                matches.get()
2371            } {
2372                for cap in m.captures {
2373                    if cap.index == idx {
2374                        vis_ranges.push(cap.node.byte_range());
2375                    }
2376                }
2377            }
2378        }
2379    }
2380
2381    let is_pub = |node: &Node| -> bool {
2382        // Check if the node has a visibility_modifier as a direct child
2383        let mut child_cursor = node.walk();
2384        if child_cursor.goto_first_child() {
2385            loop {
2386                if child_cursor.node().kind() == "visibility_modifier" {
2387                    return true;
2388                }
2389                if !child_cursor.goto_next_sibling() {
2390                    break;
2391                }
2392            }
2393        }
2394        false
2395    };
2396
2397    let mut symbols = Vec::new();
2398    let mut cursor = QueryCursor::new();
2399    let mut matches = cursor.matches(query, *root, source.as_bytes());
2400
2401    while let Some(m) = {
2402        matches.advance();
2403        matches.get()
2404    } {
2405        let mut fn_name_node = None;
2406        let mut fn_def_node = None;
2407        let mut struct_name_node = None;
2408        let mut struct_def_node = None;
2409        let mut enum_name_node = None;
2410        let mut enum_def_node = None;
2411        let mut trait_name_node = None;
2412        let mut trait_def_node = None;
2413        let mut impl_def_node = None;
2414
2415        for cap in m.captures {
2416            let Some(&name) = capture_names.get(cap.index as usize) else {
2417                continue;
2418            };
2419            match name {
2420                "fn.name" => fn_name_node = Some(cap.node),
2421                "fn.def" => fn_def_node = Some(cap.node),
2422                "struct.name" => struct_name_node = Some(cap.node),
2423                "struct.def" => struct_def_node = Some(cap.node),
2424                "enum.name" => enum_name_node = Some(cap.node),
2425                "enum.def" => enum_def_node = Some(cap.node),
2426                "trait.name" => trait_name_node = Some(cap.node),
2427                "trait.def" => trait_def_node = Some(cap.node),
2428                "impl.def" => impl_def_node = Some(cap.node),
2429                _ => {}
2430            }
2431        }
2432
2433        // Free function (skip impl/trait declaration lists; inline mod bodies are still free fns)
2434        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2435            let declaration_list_owner = def_node
2436                .parent()
2437                .filter(|parent| parent.kind() == "declaration_list")
2438                .and_then(|parent| parent.parent());
2439            let in_non_module_declaration_list = declaration_list_owner
2440                .as_ref()
2441                .is_some_and(|owner| owner.kind() != "mod_item");
2442
2443            if !in_non_module_declaration_list {
2444                let scope_chain = rust_mod_scope_chain(&def_node, source);
2445                symbols.push(Symbol {
2446                    name: node_text(source, &name_node).to_string(),
2447                    kind: SymbolKind::Function,
2448                    range: node_range_with_decorators(&def_node, source, lang),
2449                    signature: Some(extract_signature(source, &def_node)),
2450                    scope_chain: scope_chain.clone(),
2451                    exported: is_pub(&def_node),
2452                    parent: scope_chain.last().cloned(),
2453                });
2454            }
2455        }
2456
2457        // Struct
2458        if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
2459            symbols.push(Symbol {
2460                name: node_text(source, &name_node).to_string(),
2461                kind: SymbolKind::Struct,
2462                range: node_range_with_decorators(&def_node, source, lang),
2463                signature: Some(extract_signature(source, &def_node)),
2464                scope_chain: vec![],
2465                exported: is_pub(&def_node),
2466                parent: None,
2467            });
2468        }
2469
2470        // Enum
2471        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
2472            symbols.push(Symbol {
2473                name: node_text(source, &name_node).to_string(),
2474                kind: SymbolKind::Enum,
2475                range: node_range_with_decorators(&def_node, source, lang),
2476                signature: Some(extract_signature(source, &def_node)),
2477                scope_chain: vec![],
2478                exported: is_pub(&def_node),
2479                parent: None,
2480            });
2481        }
2482
2483        // Trait (mapped to Interface kind)
2484        if let (Some(name_node), Some(def_node)) = (trait_name_node, trait_def_node) {
2485            symbols.push(Symbol {
2486                name: node_text(source, &name_node).to_string(),
2487                kind: SymbolKind::Interface,
2488                range: node_range_with_decorators(&def_node, source, lang),
2489                signature: Some(extract_signature(source, &def_node)),
2490                scope_chain: vec![],
2491                exported: is_pub(&def_node),
2492                parent: None,
2493            });
2494        }
2495
2496        // Impl block — extract methods from inside
2497        if let Some(impl_node) = impl_def_node {
2498            // Find the type name(s) from the impl
2499            // `impl TypeName { ... }` → scope = ["TypeName"]
2500            // `impl Trait for TypeName { ... }` → scope = ["Trait for TypeName"]
2501            let mut type_names: Vec<String> = Vec::new();
2502            let mut child_cursor = impl_node.walk();
2503            if child_cursor.goto_first_child() {
2504                loop {
2505                    let child = child_cursor.node();
2506                    if child.kind() == "type_identifier" || child.kind() == "generic_type" {
2507                        type_names.push(node_text(source, &child).to_string());
2508                    }
2509                    if !child_cursor.goto_next_sibling() {
2510                        break;
2511                    }
2512                }
2513            }
2514
2515            let scope_name = if type_names.len() >= 2 {
2516                // impl Trait for Type
2517                format!("{} for {}", type_names[0], type_names[1])
2518            } else if type_names.len() == 1 {
2519                type_names[0].clone()
2520            } else {
2521                String::new()
2522            };
2523
2524            let parent_name = type_names.last().cloned().unwrap_or_default();
2525
2526            // Find declaration_list and extract function_items
2527            let mut child_cursor = impl_node.walk();
2528            if child_cursor.goto_first_child() {
2529                loop {
2530                    let child = child_cursor.node();
2531                    if child.kind() == "declaration_list" {
2532                        let mut fn_cursor = child.walk();
2533                        if fn_cursor.goto_first_child() {
2534                            loop {
2535                                let fn_node = fn_cursor.node();
2536                                if fn_node.kind() == "function_item" {
2537                                    if let Some(name_node) = fn_node.child_by_field_name("name") {
2538                                        symbols.push(Symbol {
2539                                            name: node_text(source, &name_node).to_string(),
2540                                            kind: SymbolKind::Method,
2541                                            range: node_range_with_decorators(
2542                                                &fn_node, source, lang,
2543                                            ),
2544                                            signature: Some(extract_signature(source, &fn_node)),
2545                                            scope_chain: if scope_name.is_empty() {
2546                                                vec![]
2547                                            } else {
2548                                                vec![scope_name.clone()]
2549                                            },
2550                                            exported: is_pub(&fn_node),
2551                                            parent: if parent_name.is_empty() {
2552                                                None
2553                                            } else {
2554                                                Some(parent_name.clone())
2555                                            },
2556                                        });
2557                                    }
2558                                }
2559                                if !fn_cursor.goto_next_sibling() {
2560                                    break;
2561                                }
2562                            }
2563                        }
2564                    }
2565                    if !child_cursor.goto_next_sibling() {
2566                        break;
2567                    }
2568                }
2569            }
2570        }
2571    }
2572
2573    dedup_symbols(&mut symbols);
2574    Ok(symbols)
2575}
2576
2577/// Extract symbols from Go source.
2578/// Handles: functions, methods (with receiver scope chain), struct/interface types,
2579/// uppercase-first-letter export detection.
2580fn extract_go_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2581    let lang = LangId::Go;
2582    let capture_names = query.capture_names();
2583
2584    let is_go_exported = |name: &str| -> bool {
2585        name.chars()
2586            .next()
2587            .map(|c| c.is_uppercase())
2588            .unwrap_or(false)
2589    };
2590
2591    let mut symbols = Vec::new();
2592    let mut cursor = QueryCursor::new();
2593    let mut matches = cursor.matches(query, *root, source.as_bytes());
2594
2595    while let Some(m) = {
2596        matches.advance();
2597        matches.get()
2598    } {
2599        let mut fn_name_node = None;
2600        let mut fn_def_node = None;
2601        let mut method_name_node = None;
2602        let mut method_def_node = None;
2603        let mut type_name_node = None;
2604        let mut type_body_node = None;
2605        let mut type_def_node = None;
2606
2607        for cap in m.captures {
2608            let Some(&name) = capture_names.get(cap.index as usize) else {
2609                continue;
2610            };
2611            match name {
2612                "fn.name" => fn_name_node = Some(cap.node),
2613                "fn.def" => fn_def_node = Some(cap.node),
2614                "method.name" => method_name_node = Some(cap.node),
2615                "method.def" => method_def_node = Some(cap.node),
2616                "type.name" => type_name_node = Some(cap.node),
2617                "type.body" => type_body_node = Some(cap.node),
2618                "type.def" => type_def_node = Some(cap.node),
2619                _ => {}
2620            }
2621        }
2622
2623        // Function declaration
2624        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2625            let name = node_text(source, &name_node).to_string();
2626            symbols.push(Symbol {
2627                exported: is_go_exported(&name),
2628                name,
2629                kind: SymbolKind::Function,
2630                range: node_range_with_decorators(&def_node, source, lang),
2631                signature: Some(extract_signature(source, &def_node)),
2632                scope_chain: vec![],
2633                parent: None,
2634            });
2635        }
2636
2637        // Method declaration (with receiver)
2638        if let (Some(name_node), Some(def_node)) = (method_name_node, method_def_node) {
2639            let name = node_text(source, &name_node).to_string();
2640
2641            // Extract receiver type from the first parameter_list
2642            let receiver_type = extract_go_receiver_type(&def_node, source);
2643            let scope_chain = if let Some(ref rt) = receiver_type {
2644                vec![rt.clone()]
2645            } else {
2646                vec![]
2647            };
2648
2649            symbols.push(Symbol {
2650                exported: is_go_exported(&name),
2651                name,
2652                kind: SymbolKind::Method,
2653                range: node_range_with_decorators(&def_node, source, lang),
2654                signature: Some(extract_signature(source, &def_node)),
2655                scope_chain,
2656                parent: receiver_type,
2657            });
2658        }
2659
2660        // Type declarations (struct or interface)
2661        if let (Some(name_node), Some(body_node), Some(def_node)) =
2662            (type_name_node, type_body_node, type_def_node)
2663        {
2664            let name = node_text(source, &name_node).to_string();
2665            let kind = match body_node.kind() {
2666                "struct_type" => SymbolKind::Struct,
2667                "interface_type" => SymbolKind::Interface,
2668                _ => SymbolKind::TypeAlias,
2669            };
2670
2671            symbols.push(Symbol {
2672                exported: is_go_exported(&name),
2673                name,
2674                kind,
2675                range: node_range_with_decorators(&def_node, source, lang),
2676                signature: Some(extract_signature(source, &def_node)),
2677                scope_chain: vec![],
2678                parent: None,
2679            });
2680        }
2681    }
2682
2683    dedup_symbols(&mut symbols);
2684    Ok(symbols)
2685}
2686
2687/// Extract the receiver type from a Go method_declaration node.
2688/// e.g. `func (m *MyStruct) String()` → Some("MyStruct")
2689fn extract_go_receiver_type(method_node: &Node, source: &str) -> Option<String> {
2690    // The first parameter_list is the receiver
2691    let mut child_cursor = method_node.walk();
2692    if child_cursor.goto_first_child() {
2693        loop {
2694            let child = child_cursor.node();
2695            if child.kind() == "parameter_list" {
2696                // Walk into parameter_list to find type_identifier
2697                return find_type_identifier_recursive(&child, source);
2698            }
2699            if !child_cursor.goto_next_sibling() {
2700                break;
2701            }
2702        }
2703    }
2704    None
2705}
2706
2707fn split_scope_text(text: &str, separator: &str) -> Vec<String> {
2708    text.split(separator)
2709        .map(str::trim)
2710        .filter(|segment| !segment.is_empty())
2711        .map(ToString::to_string)
2712        .collect()
2713}
2714
2715fn last_scope_segment(text: &str, separator: &str) -> String {
2716    split_scope_text(text, separator)
2717        .pop()
2718        .unwrap_or_else(|| text.trim().to_string())
2719}
2720
2721fn zig_container_scope_chain(node: &Node, source: &str) -> Vec<String> {
2722    let mut chain = Vec::new();
2723    let mut current = node.parent();
2724
2725    while let Some(parent) = current {
2726        if matches!(
2727            parent.kind(),
2728            "struct_declaration" | "enum_declaration" | "union_declaration" | "opaque_declaration"
2729        ) {
2730            if let Some(container) = parent.parent() {
2731                if container.kind() == "variable_declaration" {
2732                    let mut cursor = container.walk();
2733                    if cursor.goto_first_child() {
2734                        loop {
2735                            let child = cursor.node();
2736                            if child.kind() == "identifier" {
2737                                chain.push(node_text(source, &child).to_string());
2738                                break;
2739                            }
2740                            if !cursor.goto_next_sibling() {
2741                                break;
2742                            }
2743                        }
2744                    }
2745                }
2746            }
2747        }
2748        current = parent.parent();
2749    }
2750
2751    chain.reverse();
2752    chain
2753}
2754
2755fn csharp_scope_chain(node: &Node, source: &str) -> Vec<String> {
2756    let mut chain = Vec::new();
2757    let mut current = node.parent();
2758
2759    while let Some(parent) = current {
2760        match parent.kind() {
2761            "namespace_declaration" | "file_scoped_namespace_declaration" => {
2762                if let Some(name_node) = parent.child_by_field_name("name") {
2763                    chain.push(node_text(source, &name_node).to_string());
2764                }
2765            }
2766            "class_declaration"
2767            | "interface_declaration"
2768            | "struct_declaration"
2769            | "record_declaration" => {
2770                if let Some(name_node) = parent.child_by_field_name("name") {
2771                    chain.push(node_text(source, &name_node).to_string());
2772                }
2773            }
2774            _ => {}
2775        }
2776        current = parent.parent();
2777    }
2778
2779    chain.reverse();
2780    chain
2781}
2782
2783fn cpp_parent_scope_chain(node: &Node, source: &str) -> Vec<String> {
2784    let mut chain = Vec::new();
2785    let mut current = node.parent();
2786
2787    while let Some(parent) = current {
2788        match parent.kind() {
2789            "namespace_definition" => {
2790                if let Some(name_node) = parent.child_by_field_name("name") {
2791                    chain.push(node_text(source, &name_node).to_string());
2792                }
2793            }
2794            "class_specifier" | "struct_specifier" => {
2795                if let Some(name_node) = parent.child_by_field_name("name") {
2796                    chain.push(last_scope_segment(node_text(source, &name_node), "::"));
2797                }
2798            }
2799            _ => {}
2800        }
2801        current = parent.parent();
2802    }
2803
2804    chain.reverse();
2805    chain
2806}
2807
2808fn template_signature(source: &str, template_node: &Node, item_node: &Node) -> String {
2809    format!(
2810        "{}\n{}",
2811        extract_signature(source, template_node),
2812        extract_signature(source, item_node)
2813    )
2814}
2815
2816/// Extract symbols from C source.
2817fn extract_c_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2818    let lang = LangId::C;
2819    let capture_names = query.capture_names();
2820
2821    let mut symbols = Vec::new();
2822    let mut cursor = QueryCursor::new();
2823    let mut matches = cursor.matches(query, *root, source.as_bytes());
2824
2825    while let Some(m) = {
2826        matches.advance();
2827        matches.get()
2828    } {
2829        let mut fn_name_node = None;
2830        let mut fn_def_node = None;
2831        let mut struct_name_node = None;
2832        let mut struct_def_node = None;
2833        let mut enum_name_node = None;
2834        let mut enum_def_node = None;
2835        let mut type_name_node = None;
2836        let mut type_def_node = None;
2837        let mut macro_name_node = None;
2838        let mut macro_def_node = None;
2839
2840        for cap in m.captures {
2841            let Some(&name) = capture_names.get(cap.index as usize) else {
2842                continue;
2843            };
2844            match name {
2845                "fn.name" => fn_name_node = Some(cap.node),
2846                "fn.def" => fn_def_node = Some(cap.node),
2847                "struct.name" => struct_name_node = Some(cap.node),
2848                "struct.def" => struct_def_node = Some(cap.node),
2849                "enum.name" => enum_name_node = Some(cap.node),
2850                "enum.def" => enum_def_node = Some(cap.node),
2851                "type.name" => type_name_node = Some(cap.node),
2852                "type.def" => type_def_node = Some(cap.node),
2853                "macro.name" => macro_name_node = Some(cap.node),
2854                "macro.def" => macro_def_node = Some(cap.node),
2855                _ => {}
2856            }
2857        }
2858
2859        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2860            symbols.push(Symbol {
2861                name: node_text(source, &name_node).to_string(),
2862                kind: SymbolKind::Function,
2863                range: node_range_with_decorators(&def_node, source, lang),
2864                signature: Some(extract_signature(source, &def_node)),
2865                scope_chain: vec![],
2866                exported: false,
2867                parent: None,
2868            });
2869        }
2870
2871        if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
2872            symbols.push(Symbol {
2873                name: node_text(source, &name_node).to_string(),
2874                kind: SymbolKind::Struct,
2875                range: node_range_with_decorators(&def_node, source, lang),
2876                signature: Some(extract_signature(source, &def_node)),
2877                scope_chain: vec![],
2878                exported: false,
2879                parent: None,
2880            });
2881        }
2882
2883        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
2884            symbols.push(Symbol {
2885                name: node_text(source, &name_node).to_string(),
2886                kind: SymbolKind::Enum,
2887                range: node_range_with_decorators(&def_node, source, lang),
2888                signature: Some(extract_signature(source, &def_node)),
2889                scope_chain: vec![],
2890                exported: false,
2891                parent: None,
2892            });
2893        }
2894
2895        if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
2896            symbols.push(Symbol {
2897                name: node_text(source, &name_node).to_string(),
2898                kind: SymbolKind::TypeAlias,
2899                range: node_range_with_decorators(&def_node, source, lang),
2900                signature: Some(extract_signature(source, &def_node)),
2901                scope_chain: vec![],
2902                exported: false,
2903                parent: None,
2904            });
2905        }
2906
2907        if let (Some(name_node), Some(def_node)) = (macro_name_node, macro_def_node) {
2908            symbols.push(Symbol {
2909                name: node_text(source, &name_node).to_string(),
2910                kind: SymbolKind::Variable,
2911                range: node_range(&def_node),
2912                signature: Some(extract_signature(source, &def_node)),
2913                scope_chain: vec![],
2914                exported: false,
2915                parent: None,
2916            });
2917        }
2918    }
2919
2920    dedup_symbols(&mut symbols);
2921    Ok(symbols)
2922}
2923
2924/// Extract symbols from C++ source.
2925fn extract_cpp_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2926    let lang = LangId::Cpp;
2927    let capture_names = query.capture_names();
2928
2929    let mut type_names = HashSet::new();
2930    {
2931        let mut cursor = QueryCursor::new();
2932        let mut matches = cursor.matches(query, *root, source.as_bytes());
2933        while let Some(m) = {
2934            matches.advance();
2935            matches.get()
2936        } {
2937            for cap in m.captures {
2938                let Some(&name) = capture_names.get(cap.index as usize) else {
2939                    continue;
2940                };
2941                match name {
2942                    "class.name"
2943                    | "struct.name"
2944                    | "template.class.name"
2945                    | "template.struct.name" => {
2946                        type_names.insert(last_scope_segment(node_text(source, &cap.node), "::"));
2947                    }
2948                    _ => {}
2949                }
2950            }
2951        }
2952    }
2953
2954    let mut symbols = Vec::new();
2955    let mut cursor = QueryCursor::new();
2956    let mut matches = cursor.matches(query, *root, source.as_bytes());
2957
2958    while let Some(m) = {
2959        matches.advance();
2960        matches.get()
2961    } {
2962        let mut fn_name_node = None;
2963        let mut fn_def_node = None;
2964        let mut method_name_node = None;
2965        let mut method_def_node = None;
2966        let mut qual_scope_node = None;
2967        let mut qual_name_node = None;
2968        let mut qual_def_node = None;
2969        let mut class_name_node = None;
2970        let mut class_def_node = None;
2971        let mut struct_name_node = None;
2972        let mut struct_def_node = None;
2973        let mut enum_name_node = None;
2974        let mut enum_def_node = None;
2975        let mut namespace_name_node = None;
2976        let mut namespace_def_node = None;
2977        let mut template_class_name_node = None;
2978        let mut template_class_def_node = None;
2979        let mut template_class_item_node = None;
2980        let mut template_struct_name_node = None;
2981        let mut template_struct_def_node = None;
2982        let mut template_struct_item_node = None;
2983        let mut template_fn_name_node = None;
2984        let mut template_fn_def_node = None;
2985        let mut template_fn_item_node = None;
2986        let mut template_qual_scope_node = None;
2987        let mut template_qual_name_node = None;
2988        let mut template_qual_def_node = None;
2989        let mut template_qual_item_node = None;
2990
2991        for cap in m.captures {
2992            let Some(&name) = capture_names.get(cap.index as usize) else {
2993                continue;
2994            };
2995            match name {
2996                "fn.name" => fn_name_node = Some(cap.node),
2997                "fn.def" => fn_def_node = Some(cap.node),
2998                "method.name" => method_name_node = Some(cap.node),
2999                "method.def" => method_def_node = Some(cap.node),
3000                "qual.scope" => qual_scope_node = Some(cap.node),
3001                "qual.name" => qual_name_node = Some(cap.node),
3002                "qual.def" => qual_def_node = Some(cap.node),
3003                "class.name" => class_name_node = Some(cap.node),
3004                "class.def" => class_def_node = Some(cap.node),
3005                "struct.name" => struct_name_node = Some(cap.node),
3006                "struct.def" => struct_def_node = Some(cap.node),
3007                "enum.name" => enum_name_node = Some(cap.node),
3008                "enum.def" => enum_def_node = Some(cap.node),
3009                "namespace.name" => namespace_name_node = Some(cap.node),
3010                "namespace.def" => namespace_def_node = Some(cap.node),
3011                "template.class.name" => template_class_name_node = Some(cap.node),
3012                "template.class.def" => template_class_def_node = Some(cap.node),
3013                "template.class.item" => template_class_item_node = Some(cap.node),
3014                "template.struct.name" => template_struct_name_node = Some(cap.node),
3015                "template.struct.def" => template_struct_def_node = Some(cap.node),
3016                "template.struct.item" => template_struct_item_node = Some(cap.node),
3017                "template.fn.name" => template_fn_name_node = Some(cap.node),
3018                "template.fn.def" => template_fn_def_node = Some(cap.node),
3019                "template.fn.item" => template_fn_item_node = Some(cap.node),
3020                "template.qual.scope" => template_qual_scope_node = Some(cap.node),
3021                "template.qual.name" => template_qual_name_node = Some(cap.node),
3022                "template.qual.def" => template_qual_def_node = Some(cap.node),
3023                "template.qual.item" => template_qual_item_node = Some(cap.node),
3024                _ => {}
3025            }
3026        }
3027
3028        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
3029            let in_template = def_node
3030                .parent()
3031                .map(|parent| parent.kind() == "template_declaration")
3032                .unwrap_or(false);
3033            if !in_template {
3034                let scope_chain = cpp_parent_scope_chain(&def_node, source);
3035                symbols.push(Symbol {
3036                    name: node_text(source, &name_node).to_string(),
3037                    kind: SymbolKind::Function,
3038                    range: node_range_with_decorators(&def_node, source, lang),
3039                    signature: Some(extract_signature(source, &def_node)),
3040                    scope_chain: scope_chain.clone(),
3041                    exported: false,
3042                    parent: scope_chain.last().cloned(),
3043                });
3044            }
3045        }
3046
3047        if let (Some(name_node), Some(def_node)) = (method_name_node, method_def_node) {
3048            let scope_chain = cpp_parent_scope_chain(&def_node, source);
3049            symbols.push(Symbol {
3050                name: node_text(source, &name_node).to_string(),
3051                kind: SymbolKind::Method,
3052                range: node_range_with_decorators(&def_node, source, lang),
3053                signature: Some(extract_signature(source, &def_node)),
3054                scope_chain: scope_chain.clone(),
3055                exported: false,
3056                parent: scope_chain.last().cloned(),
3057            });
3058        }
3059
3060        if let (Some(scope_node), Some(name_node), Some(def_node)) =
3061            (qual_scope_node, qual_name_node, qual_def_node)
3062        {
3063            let in_template = def_node
3064                .parent()
3065                .map(|parent| parent.kind() == "template_declaration")
3066                .unwrap_or(false);
3067            if !in_template {
3068                let scope_text = node_text(source, &scope_node);
3069                let scope_chain = split_scope_text(scope_text, "::");
3070                let parent = scope_chain.last().cloned();
3071                let kind = if parent
3072                    .as_ref()
3073                    .map(|segment| type_names.contains(segment))
3074                    .unwrap_or(false)
3075                {
3076                    SymbolKind::Method
3077                } else {
3078                    SymbolKind::Function
3079                };
3080
3081                symbols.push(Symbol {
3082                    name: node_text(source, &name_node).to_string(),
3083                    kind,
3084                    range: node_range_with_decorators(&def_node, source, lang),
3085                    signature: Some(extract_signature(source, &def_node)),
3086                    scope_chain,
3087                    exported: false,
3088                    parent,
3089                });
3090            }
3091        }
3092
3093        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
3094            let in_template = def_node
3095                .parent()
3096                .map(|parent| parent.kind() == "template_declaration")
3097                .unwrap_or(false);
3098            if !in_template {
3099                let scope_chain = cpp_parent_scope_chain(&def_node, source);
3100                let name = last_scope_segment(node_text(source, &name_node), "::");
3101                symbols.push(Symbol {
3102                    name: name.clone(),
3103                    kind: SymbolKind::Class,
3104                    range: node_range_with_decorators(&def_node, source, lang),
3105                    signature: Some(extract_signature(source, &def_node)),
3106                    scope_chain: scope_chain.clone(),
3107                    exported: false,
3108                    parent: scope_chain.last().cloned(),
3109                });
3110            }
3111        }
3112
3113        if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
3114            let in_template = def_node
3115                .parent()
3116                .map(|parent| parent.kind() == "template_declaration")
3117                .unwrap_or(false);
3118            if !in_template {
3119                let scope_chain = cpp_parent_scope_chain(&def_node, source);
3120                let name = last_scope_segment(node_text(source, &name_node), "::");
3121                symbols.push(Symbol {
3122                    name: name.clone(),
3123                    kind: SymbolKind::Struct,
3124                    range: node_range_with_decorators(&def_node, source, lang),
3125                    signature: Some(extract_signature(source, &def_node)),
3126                    scope_chain: scope_chain.clone(),
3127                    exported: false,
3128                    parent: scope_chain.last().cloned(),
3129                });
3130            }
3131        }
3132
3133        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
3134            let scope_chain = cpp_parent_scope_chain(&def_node, source);
3135            let name = last_scope_segment(node_text(source, &name_node), "::");
3136            symbols.push(Symbol {
3137                name: name.clone(),
3138                kind: SymbolKind::Enum,
3139                range: node_range_with_decorators(&def_node, source, lang),
3140                signature: Some(extract_signature(source, &def_node)),
3141                scope_chain: scope_chain.clone(),
3142                exported: false,
3143                parent: scope_chain.last().cloned(),
3144            });
3145        }
3146
3147        if let (Some(name_node), Some(def_node)) = (namespace_name_node, namespace_def_node) {
3148            let scope_chain = cpp_parent_scope_chain(&def_node, source);
3149            symbols.push(Symbol {
3150                name: node_text(source, &name_node).to_string(),
3151                kind: SymbolKind::TypeAlias,
3152                range: node_range_with_decorators(&def_node, source, lang),
3153                signature: Some(extract_signature(source, &def_node)),
3154                scope_chain: scope_chain.clone(),
3155                exported: false,
3156                parent: scope_chain.last().cloned(),
3157            });
3158        }
3159
3160        if let (Some(name_node), Some(def_node), Some(item_node)) = (
3161            template_class_name_node,
3162            template_class_def_node,
3163            template_class_item_node,
3164        ) {
3165            let scope_chain = cpp_parent_scope_chain(&def_node, source);
3166            let name = last_scope_segment(node_text(source, &name_node), "::");
3167            symbols.push(Symbol {
3168                name: name.clone(),
3169                kind: SymbolKind::Class,
3170                range: node_range_with_decorators(&def_node, source, lang),
3171                signature: Some(template_signature(source, &def_node, &item_node)),
3172                scope_chain: scope_chain.clone(),
3173                exported: false,
3174                parent: scope_chain.last().cloned(),
3175            });
3176        }
3177
3178        if let (Some(name_node), Some(def_node), Some(item_node)) = (
3179            template_struct_name_node,
3180            template_struct_def_node,
3181            template_struct_item_node,
3182        ) {
3183            let scope_chain = cpp_parent_scope_chain(&def_node, source);
3184            let name = last_scope_segment(node_text(source, &name_node), "::");
3185            symbols.push(Symbol {
3186                name: name.clone(),
3187                kind: SymbolKind::Struct,
3188                range: node_range_with_decorators(&def_node, source, lang),
3189                signature: Some(template_signature(source, &def_node, &item_node)),
3190                scope_chain: scope_chain.clone(),
3191                exported: false,
3192                parent: scope_chain.last().cloned(),
3193            });
3194        }
3195
3196        if let (Some(name_node), Some(def_node), Some(item_node)) = (
3197            template_fn_name_node,
3198            template_fn_def_node,
3199            template_fn_item_node,
3200        ) {
3201            let scope_chain = cpp_parent_scope_chain(&def_node, source);
3202            symbols.push(Symbol {
3203                name: node_text(source, &name_node).to_string(),
3204                kind: SymbolKind::Function,
3205                range: node_range_with_decorators(&def_node, source, lang),
3206                signature: Some(template_signature(source, &def_node, &item_node)),
3207                scope_chain: scope_chain.clone(),
3208                exported: false,
3209                parent: scope_chain.last().cloned(),
3210            });
3211        }
3212
3213        if let (Some(scope_node), Some(name_node), Some(def_node), Some(item_node)) = (
3214            template_qual_scope_node,
3215            template_qual_name_node,
3216            template_qual_def_node,
3217            template_qual_item_node,
3218        ) {
3219            let scope_chain = split_scope_text(node_text(source, &scope_node), "::");
3220            let parent = scope_chain.last().cloned();
3221            let kind = if parent
3222                .as_ref()
3223                .map(|segment| type_names.contains(segment))
3224                .unwrap_or(false)
3225            {
3226                SymbolKind::Method
3227            } else {
3228                SymbolKind::Function
3229            };
3230
3231            symbols.push(Symbol {
3232                name: node_text(source, &name_node).to_string(),
3233                kind,
3234                range: node_range_with_decorators(&def_node, source, lang),
3235                signature: Some(template_signature(source, &def_node, &item_node)),
3236                scope_chain,
3237                exported: false,
3238                parent,
3239            });
3240        }
3241    }
3242
3243    dedup_symbols(&mut symbols);
3244    Ok(symbols)
3245}
3246
3247/// Extract symbols from Zig source.
3248fn extract_zig_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
3249    let lang = LangId::Zig;
3250    let capture_names = query.capture_names();
3251
3252    let mut symbols = Vec::new();
3253    let mut cursor = QueryCursor::new();
3254    let mut matches = cursor.matches(query, *root, source.as_bytes());
3255
3256    while let Some(m) = {
3257        matches.advance();
3258        matches.get()
3259    } {
3260        let mut fn_name_node = None;
3261        let mut fn_def_node = None;
3262        let mut struct_name_node = None;
3263        let mut struct_def_node = None;
3264        let mut enum_name_node = None;
3265        let mut enum_def_node = None;
3266        let mut union_name_node = None;
3267        let mut union_def_node = None;
3268        let mut const_name_node = None;
3269        let mut const_def_node = None;
3270        let mut test_name_node = None;
3271        let mut test_def_node = None;
3272
3273        for cap in m.captures {
3274            let Some(&name) = capture_names.get(cap.index as usize) else {
3275                continue;
3276            };
3277            match name {
3278                "fn.name" => fn_name_node = Some(cap.node),
3279                "fn.def" => fn_def_node = Some(cap.node),
3280                "struct.name" => struct_name_node = Some(cap.node),
3281                "struct.def" => struct_def_node = Some(cap.node),
3282                "enum.name" => enum_name_node = Some(cap.node),
3283                "enum.def" => enum_def_node = Some(cap.node),
3284                "union.name" => union_name_node = Some(cap.node),
3285                "union.def" => union_def_node = Some(cap.node),
3286                "const.name" => const_name_node = Some(cap.node),
3287                "const.def" => const_def_node = Some(cap.node),
3288                "test.name" => test_name_node = Some(cap.node),
3289                "test.def" => test_def_node = Some(cap.node),
3290                _ => {}
3291            }
3292        }
3293
3294        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
3295            let scope_chain = zig_container_scope_chain(&def_node, source);
3296            let kind = if scope_chain.is_empty() {
3297                SymbolKind::Function
3298            } else {
3299                SymbolKind::Method
3300            };
3301            symbols.push(Symbol {
3302                name: node_text(source, &name_node).to_string(),
3303                kind,
3304                range: node_range_with_decorators(&def_node, source, lang),
3305                signature: Some(extract_signature(source, &def_node)),
3306                scope_chain: scope_chain.clone(),
3307                exported: false,
3308                parent: scope_chain.last().cloned(),
3309            });
3310        }
3311
3312        if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
3313            symbols.push(Symbol {
3314                name: node_text(source, &name_node).to_string(),
3315                kind: SymbolKind::Struct,
3316                range: node_range_with_decorators(&def_node, source, lang),
3317                signature: Some(extract_signature(source, &def_node)),
3318                scope_chain: vec![],
3319                exported: false,
3320                parent: None,
3321            });
3322        }
3323
3324        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
3325            symbols.push(Symbol {
3326                name: node_text(source, &name_node).to_string(),
3327                kind: SymbolKind::Enum,
3328                range: node_range_with_decorators(&def_node, source, lang),
3329                signature: Some(extract_signature(source, &def_node)),
3330                scope_chain: vec![],
3331                exported: false,
3332                parent: None,
3333            });
3334        }
3335
3336        if let (Some(name_node), Some(def_node)) = (union_name_node, union_def_node) {
3337            symbols.push(Symbol {
3338                name: node_text(source, &name_node).to_string(),
3339                kind: SymbolKind::TypeAlias,
3340                range: node_range_with_decorators(&def_node, source, lang),
3341                signature: Some(extract_signature(source, &def_node)),
3342                scope_chain: vec![],
3343                exported: false,
3344                parent: None,
3345            });
3346        }
3347
3348        if let (Some(name_node), Some(def_node)) = (const_name_node, const_def_node) {
3349            let signature = extract_signature(source, &def_node);
3350            let is_container = signature.contains("= struct")
3351                || signature.contains("= enum")
3352                || signature.contains("= union")
3353                || signature.contains("= opaque");
3354            let is_const = signature.trim_start().starts_with("const ");
3355            let name = node_text(source, &name_node).to_string();
3356            let already_captured = symbols.iter().any(|symbol| symbol.name == name);
3357            if is_const && !is_container && !already_captured {
3358                symbols.push(Symbol {
3359                    name,
3360                    kind: SymbolKind::Variable,
3361                    range: node_range_with_decorators(&def_node, source, lang),
3362                    signature: Some(signature),
3363                    scope_chain: vec![],
3364                    exported: false,
3365                    parent: None,
3366                });
3367            }
3368        }
3369
3370        if let (Some(name_node), Some(def_node)) = (test_name_node, test_def_node) {
3371            let scope_chain = zig_container_scope_chain(&def_node, source);
3372            symbols.push(Symbol {
3373                name: node_text(source, &name_node).trim_matches('"').to_string(),
3374                kind: SymbolKind::Function,
3375                range: node_range_with_decorators(&def_node, source, lang),
3376                signature: Some(extract_signature(source, &def_node)),
3377                scope_chain: scope_chain.clone(),
3378                exported: false,
3379                parent: scope_chain.last().cloned(),
3380            });
3381        }
3382    }
3383
3384    dedup_symbols(&mut symbols);
3385    Ok(symbols)
3386}
3387
3388/// Extract symbols from C# source.
3389fn extract_csharp_symbols(
3390    source: &str,
3391    root: &Node,
3392    query: &Query,
3393) -> Result<Vec<Symbol>, AftError> {
3394    let lang = LangId::CSharp;
3395    let capture_names = query.capture_names();
3396
3397    let mut symbols = Vec::new();
3398    let mut cursor = QueryCursor::new();
3399    let mut matches = cursor.matches(query, *root, source.as_bytes());
3400
3401    while let Some(m) = {
3402        matches.advance();
3403        matches.get()
3404    } {
3405        let mut class_name_node = None;
3406        let mut class_def_node = None;
3407        let mut interface_name_node = None;
3408        let mut interface_def_node = None;
3409        let mut struct_name_node = None;
3410        let mut struct_def_node = None;
3411        let mut enum_name_node = None;
3412        let mut enum_def_node = None;
3413        let mut method_name_node = None;
3414        let mut method_def_node = None;
3415        let mut property_name_node = None;
3416        let mut property_def_node = None;
3417        let mut namespace_name_node = None;
3418        let mut namespace_def_node = None;
3419
3420        for cap in m.captures {
3421            let Some(&name) = capture_names.get(cap.index as usize) else {
3422                continue;
3423            };
3424            match name {
3425                "class.name" => class_name_node = Some(cap.node),
3426                "class.def" => class_def_node = Some(cap.node),
3427                "interface.name" => interface_name_node = Some(cap.node),
3428                "interface.def" => interface_def_node = Some(cap.node),
3429                "struct.name" => struct_name_node = Some(cap.node),
3430                "struct.def" => struct_def_node = Some(cap.node),
3431                "enum.name" => enum_name_node = Some(cap.node),
3432                "enum.def" => enum_def_node = Some(cap.node),
3433                "method.name" => method_name_node = Some(cap.node),
3434                "method.def" => method_def_node = Some(cap.node),
3435                "property.name" => property_name_node = Some(cap.node),
3436                "property.def" => property_def_node = Some(cap.node),
3437                "namespace.name" => namespace_name_node = Some(cap.node),
3438                "namespace.def" => namespace_def_node = Some(cap.node),
3439                _ => {}
3440            }
3441        }
3442
3443        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
3444            let scope_chain = csharp_scope_chain(&def_node, source);
3445            symbols.push(Symbol {
3446                name: node_text(source, &name_node).to_string(),
3447                kind: SymbolKind::Class,
3448                range: node_range_with_decorators(&def_node, source, lang),
3449                signature: Some(extract_signature(source, &def_node)),
3450                scope_chain: scope_chain.clone(),
3451                exported: false,
3452                parent: scope_chain.last().cloned(),
3453            });
3454        }
3455
3456        if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
3457            let scope_chain = csharp_scope_chain(&def_node, source);
3458            symbols.push(Symbol {
3459                name: node_text(source, &name_node).to_string(),
3460                kind: SymbolKind::Interface,
3461                range: node_range_with_decorators(&def_node, source, lang),
3462                signature: Some(extract_signature(source, &def_node)),
3463                scope_chain: scope_chain.clone(),
3464                exported: false,
3465                parent: scope_chain.last().cloned(),
3466            });
3467        }
3468
3469        if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
3470            let scope_chain = csharp_scope_chain(&def_node, source);
3471            symbols.push(Symbol {
3472                name: node_text(source, &name_node).to_string(),
3473                kind: SymbolKind::Struct,
3474                range: node_range_with_decorators(&def_node, source, lang),
3475                signature: Some(extract_signature(source, &def_node)),
3476                scope_chain: scope_chain.clone(),
3477                exported: false,
3478                parent: scope_chain.last().cloned(),
3479            });
3480        }
3481
3482        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
3483            let scope_chain = csharp_scope_chain(&def_node, source);
3484            symbols.push(Symbol {
3485                name: node_text(source, &name_node).to_string(),
3486                kind: SymbolKind::Enum,
3487                range: node_range_with_decorators(&def_node, source, lang),
3488                signature: Some(extract_signature(source, &def_node)),
3489                scope_chain: scope_chain.clone(),
3490                exported: false,
3491                parent: scope_chain.last().cloned(),
3492            });
3493        }
3494
3495        if let (Some(name_node), Some(def_node)) = (method_name_node, method_def_node) {
3496            let scope_chain = csharp_scope_chain(&def_node, source);
3497            symbols.push(Symbol {
3498                name: node_text(source, &name_node).to_string(),
3499                kind: SymbolKind::Method,
3500                range: node_range_with_decorators(&def_node, source, lang),
3501                signature: Some(extract_signature(source, &def_node)),
3502                scope_chain: scope_chain.clone(),
3503                exported: false,
3504                parent: scope_chain.last().cloned(),
3505            });
3506        }
3507
3508        if let (Some(name_node), Some(def_node)) = (property_name_node, property_def_node) {
3509            let scope_chain = csharp_scope_chain(&def_node, source);
3510            symbols.push(Symbol {
3511                name: node_text(source, &name_node).to_string(),
3512                kind: SymbolKind::Variable,
3513                range: node_range_with_decorators(&def_node, source, lang),
3514                signature: Some(extract_signature(source, &def_node)),
3515                scope_chain: scope_chain.clone(),
3516                exported: false,
3517                parent: scope_chain.last().cloned(),
3518            });
3519        }
3520
3521        if let (Some(name_node), Some(def_node)) = (namespace_name_node, namespace_def_node) {
3522            let scope_chain = csharp_scope_chain(&def_node, source);
3523            symbols.push(Symbol {
3524                name: node_text(source, &name_node).to_string(),
3525                kind: SymbolKind::TypeAlias,
3526                range: node_range_with_decorators(&def_node, source, lang),
3527                signature: Some(extract_signature(source, &def_node)),
3528                scope_chain: scope_chain.clone(),
3529                exported: false,
3530                parent: scope_chain.last().cloned(),
3531            });
3532        }
3533    }
3534
3535    dedup_symbols(&mut symbols);
3536    Ok(symbols)
3537}
3538
3539/// Recursively find the first type_identifier node in a subtree.
3540fn find_type_identifier_recursive(node: &Node, source: &str) -> Option<String> {
3541    if node.kind() == "type_identifier" {
3542        return Some(node_text(source, node).to_string());
3543    }
3544    let mut cursor = node.walk();
3545    if cursor.goto_first_child() {
3546        loop {
3547            if let Some(result) = find_type_identifier_recursive(&cursor.node(), source) {
3548                return Some(result);
3549            }
3550            if !cursor.goto_next_sibling() {
3551                break;
3552            }
3553        }
3554    }
3555    None
3556}
3557
3558/// Extract HTML headings (h1-h6) as symbols.
3559/// Each heading becomes a symbol with kind `Heading`, and its range covers
3560/// the element itself. Headings are nested based on their level.
3561fn extract_bash_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
3562    let lang = LangId::Bash;
3563    let capture_names = query.capture_names();
3564
3565    let mut symbols = Vec::new();
3566    let mut cursor = QueryCursor::new();
3567    let mut matches = cursor.matches(query, *root, source.as_bytes());
3568
3569    while let Some(m) = {
3570        matches.advance();
3571        matches.get()
3572    } {
3573        let mut fn_name_node = None;
3574        let mut fn_def_node = None;
3575
3576        for cap in m.captures {
3577            let Some(&name) = capture_names.get(cap.index as usize) else {
3578                continue;
3579            };
3580            match name {
3581                "fn.name" => fn_name_node = Some(cap.node),
3582                "fn.def" => fn_def_node = Some(cap.node),
3583                _ => {}
3584            }
3585        }
3586
3587        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
3588            symbols.push(Symbol {
3589                name: node_text(source, &name_node).to_string(),
3590                kind: SymbolKind::Function,
3591                range: node_range_with_decorators(&def_node, source, lang),
3592                signature: Some(extract_signature(source, &def_node)),
3593                scope_chain: vec![],
3594                exported: false,
3595                parent: None,
3596            });
3597        }
3598    }
3599
3600    Ok(symbols)
3601}
3602
3603fn extract_pascal_symbols(
3604    source: &str,
3605    root: &Node,
3606    query: &Query,
3607) -> Result<Vec<Symbol>, AftError> {
3608    let lang = LangId::Pascal;
3609    let capture_names = query.capture_names();
3610
3611    let mut symbols = Vec::new();
3612    let mut cursor = QueryCursor::new();
3613    let mut matches = cursor.matches(query, *root, source.as_bytes());
3614
3615    while let Some(m) = {
3616        matches.advance();
3617        matches.get()
3618    } {
3619        let mut program_name_node = None;
3620        let mut program_def_node = None;
3621        let mut unit_name_node = None;
3622        let mut unit_def_node = None;
3623        let mut type_name_node = None;
3624        let mut type_def_node = None;
3625        let mut const_name_node = None;
3626        let mut const_def_node = None;
3627        let mut var_name_node = None;
3628        let mut var_def_node = None;
3629        let mut proc_name_node = None;
3630        let mut proc_def_node = None;
3631
3632        for cap in m.captures {
3633            let Some(&name) = capture_names.get(cap.index as usize) else {
3634                continue;
3635            };
3636            match name {
3637                "program.name" => program_name_node = Some(cap.node),
3638                "program.def" => program_def_node = Some(cap.node),
3639                "unit.name" => unit_name_node = Some(cap.node),
3640                "unit.def" => unit_def_node = Some(cap.node),
3641                "type.name" => type_name_node = Some(cap.node),
3642                "type.def" => type_def_node = Some(cap.node),
3643                "const.name" => const_name_node = Some(cap.node),
3644                "const.def" => const_def_node = Some(cap.node),
3645                "var.name" => var_name_node = Some(cap.node),
3646                "var.def" => var_def_node = Some(cap.node),
3647                "proc.name" => proc_name_node = Some(cap.node),
3648                "proc.def" => proc_def_node = Some(cap.node),
3649                _ => {}
3650            }
3651        }
3652
3653        if let (Some(name_node), Some(def_node)) = (program_name_node, program_def_node) {
3654            symbols.push(Symbol {
3655                name: node_text(source, &name_node).to_string(),
3656                kind: SymbolKind::Class,
3657                range: node_range_with_decorators(&def_node, source, lang),
3658                signature: Some(extract_signature(source, &def_node)),
3659                scope_chain: vec![],
3660                exported: true,
3661                parent: None,
3662            });
3663        }
3664
3665        if let (Some(name_node), Some(def_node)) = (unit_name_node, unit_def_node) {
3666            symbols.push(Symbol {
3667                name: node_text(source, &name_node).to_string(),
3668                kind: SymbolKind::Class,
3669                range: node_range_with_decorators(&def_node, source, lang),
3670                signature: Some(extract_signature(source, &def_node)),
3671                scope_chain: vec![],
3672                exported: true,
3673                parent: None,
3674            });
3675        }
3676
3677        if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
3678            let kind = pascal_type_kind(&def_node, source);
3679            symbols.push(Symbol {
3680                name: node_text(source, &name_node).to_string(),
3681                kind,
3682                range: node_range_with_decorators(&def_node, source, lang),
3683                signature: Some(extract_signature(source, &def_node)),
3684                scope_chain: vec![],
3685                exported: true,
3686                parent: None,
3687            });
3688        }
3689
3690        if let (Some(name_node), Some(def_node)) = (const_name_node, const_def_node) {
3691            symbols.push(Symbol {
3692                name: node_text(source, &name_node).to_string(),
3693                kind: SymbolKind::Variable,
3694                range: node_range_with_decorators(&def_node, source, lang),
3695                signature: Some(extract_signature(source, &def_node)),
3696                scope_chain: vec![],
3697                exported: true,
3698                parent: None,
3699            });
3700        }
3701
3702        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
3703            symbols.push(Symbol {
3704                name: node_text(source, &name_node).to_string(),
3705                kind: SymbolKind::Variable,
3706                range: node_range_with_decorators(&def_node, source, lang),
3707                signature: Some(extract_signature(source, &def_node)),
3708                scope_chain: vec![],
3709                exported: true,
3710                parent: None,
3711            });
3712        }
3713
3714        if let (Some(name_node), Some(def_node)) = (proc_name_node, proc_def_node) {
3715            if def_node.kind() == "declProc" {
3716                if let Some(parent) = def_node.parent() {
3717                    if parent.kind() == "defProc" {
3718                        continue;
3719                    }
3720                }
3721            }
3722
3723            let (name, scope_chain) = extract_pascal_name_and_scope(&name_node, &def_node, source);
3724            let kind = if scope_chain.is_empty() {
3725                SymbolKind::Function
3726            } else {
3727                SymbolKind::Method
3728            };
3729
3730            symbols.push(Symbol {
3731                name,
3732                kind,
3733                range: node_range_with_decorators(&def_node, source, lang),
3734                signature: Some(extract_signature(source, &def_node)),
3735                parent: scope_chain.last().cloned(),
3736                scope_chain,
3737                exported: true,
3738            });
3739        }
3740    }
3741
3742    dedup_symbols(&mut symbols);
3743    Ok(symbols)
3744}
3745
3746fn pascal_type_kind(node: &Node, _source: &str) -> SymbolKind {
3747    let mut cursor = node.walk();
3748    for child in node.children(&mut cursor) {
3749        if child.kind() == "declClass" {
3750            let mut child_cursor = child.walk();
3751            for grandchild in child.children(&mut child_cursor) {
3752                if grandchild.kind() == "kRecord" {
3753                    return SymbolKind::Struct;
3754                }
3755            }
3756            return SymbolKind::Class;
3757        }
3758        if child.kind() == "declIntf" {
3759            return SymbolKind::Interface;
3760        }
3761        if child.kind() == "type" {
3762            let mut child_cursor = child.walk();
3763            for grandchild in child.children(&mut child_cursor) {
3764                if grandchild.kind() == "declEnum" {
3765                    return SymbolKind::Enum;
3766                }
3767            }
3768        }
3769    }
3770    SymbolKind::TypeAlias
3771}
3772
3773fn extract_pascal_name_and_scope(
3774    name_node: &Node,
3775    def_node: &Node,
3776    source: &str,
3777) -> (String, Vec<String>) {
3778    let mut scope_chain = Vec::new();
3779    let mut name = node_text(source, name_node).to_string();
3780
3781    if name_node.kind() == "genericDot" {
3782        let mut cursor = name_node.walk();
3783        let idents: Vec<Node> = name_node
3784            .children(&mut cursor)
3785            .filter(|c| c.kind() == "identifier")
3786            .collect();
3787        if idents.len() >= 2 {
3788            let class_name = node_text(source, &idents[0]).to_string();
3789            scope_chain.push(class_name);
3790            name = node_text(source, &idents[1]).to_string();
3791        }
3792    }
3793
3794    let mut current = def_node.parent();
3795    while let Some(parent) = current {
3796        if parent.kind() == "declType" {
3797            if let Some(type_name_node) = parent.child_by_field_name("name").or_else(|| {
3798                let mut cursor = parent.walk();
3799                let mut found = None;
3800                for child in parent.children(&mut cursor) {
3801                    if child.kind() == "identifier" {
3802                        found = Some(child);
3803                        break;
3804                    }
3805                }
3806                found
3807            }) {
3808                scope_chain.insert(0, node_text(source, &type_name_node).to_string());
3809            }
3810        }
3811        current = parent.parent();
3812    }
3813
3814    (name, scope_chain)
3815}
3816
3817/// Walk up from `node` and collect the names of any enclosing
3818/// contract / library / interface, outermost first.
3819fn solidity_scope_chain(node: &Node, source: &str) -> Vec<String> {
3820    let mut chain = Vec::new();
3821    let mut current = node.parent();
3822
3823    while let Some(parent) = current {
3824        match parent.kind() {
3825            "contract_declaration" | "library_declaration" | "interface_declaration" => {
3826                if let Some(name_node) = parent.child_by_field_name("name") {
3827                    chain.push(node_text(source, &name_node).to_string());
3828                }
3829            }
3830            _ => {}
3831        }
3832        current = parent.parent();
3833    }
3834
3835    chain.reverse();
3836    chain
3837}
3838
3839fn extract_solidity_symbols(
3840    source: &str,
3841    root: &Node,
3842    query: &Query,
3843) -> Result<Vec<Symbol>, AftError> {
3844    let lang = LangId::Solidity;
3845    let capture_names = query.capture_names();
3846
3847    let mut symbols = Vec::new();
3848    let mut cursor = QueryCursor::new();
3849    let mut matches = cursor.matches(query, *root, source.as_bytes());
3850
3851    while let Some(m) = {
3852        matches.advance();
3853        matches.get()
3854    } {
3855        let mut contract_name_node = None;
3856        let mut contract_def_node = None;
3857        let mut library_name_node = None;
3858        let mut library_def_node = None;
3859        let mut interface_name_node = None;
3860        let mut interface_def_node = None;
3861        let mut fn_name_node = None;
3862        let mut fn_def_node = None;
3863        let mut modifier_name_node = None;
3864        let mut modifier_def_node = None;
3865        let mut constructor_def_node = None;
3866        let mut fallback_receive_def_node = None;
3867        let mut event_name_node = None;
3868        let mut event_def_node = None;
3869        let mut error_name_node = None;
3870        let mut error_def_node = None;
3871        let mut struct_name_node = None;
3872        let mut struct_def_node = None;
3873        let mut enum_name_node = None;
3874        let mut enum_def_node = None;
3875        let mut var_name_node = None;
3876        let mut var_def_node = None;
3877
3878        for cap in m.captures {
3879            let Some(&name) = capture_names.get(cap.index as usize) else {
3880                continue;
3881            };
3882            match name {
3883                "contract.name" => contract_name_node = Some(cap.node),
3884                "contract.def" => contract_def_node = Some(cap.node),
3885                "library.name" => library_name_node = Some(cap.node),
3886                "library.def" => library_def_node = Some(cap.node),
3887                "interface.name" => interface_name_node = Some(cap.node),
3888                "interface.def" => interface_def_node = Some(cap.node),
3889                "fn.name" => fn_name_node = Some(cap.node),
3890                "fn.def" => fn_def_node = Some(cap.node),
3891                "modifier.name" => modifier_name_node = Some(cap.node),
3892                "modifier.def" => modifier_def_node = Some(cap.node),
3893                "constructor.def" => constructor_def_node = Some(cap.node),
3894                "fallback_receive.def" => fallback_receive_def_node = Some(cap.node),
3895                "event.name" => event_name_node = Some(cap.node),
3896                "event.def" => event_def_node = Some(cap.node),
3897                "error.name" => error_name_node = Some(cap.node),
3898                "error.def" => error_def_node = Some(cap.node),
3899                "struct.name" => struct_name_node = Some(cap.node),
3900                "struct.def" => struct_def_node = Some(cap.node),
3901                "enum.name" => enum_name_node = Some(cap.node),
3902                "enum.def" => enum_def_node = Some(cap.node),
3903                "var.name" => var_name_node = Some(cap.node),
3904                "var.def" => var_def_node = Some(cap.node),
3905                _ => {}
3906            }
3907        }
3908
3909        // Contract
3910        if let (Some(name_node), Some(def_node)) = (contract_name_node, contract_def_node) {
3911            symbols.push(Symbol {
3912                name: node_text(source, &name_node).to_string(),
3913                kind: SymbolKind::Class,
3914                range: node_range_with_decorators(&def_node, source, lang),
3915                signature: Some(extract_signature(source, &def_node)),
3916                scope_chain: vec![],
3917                exported: true,
3918                parent: None,
3919            });
3920        }
3921
3922        // Library (treated like a contract — class-shaped container)
3923        if let (Some(name_node), Some(def_node)) = (library_name_node, library_def_node) {
3924            symbols.push(Symbol {
3925                name: node_text(source, &name_node).to_string(),
3926                kind: SymbolKind::Class,
3927                range: node_range_with_decorators(&def_node, source, lang),
3928                signature: Some(extract_signature(source, &def_node)),
3929                scope_chain: vec![],
3930                exported: true,
3931                parent: None,
3932            });
3933        }
3934
3935        // Interface
3936        if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
3937            symbols.push(Symbol {
3938                name: node_text(source, &name_node).to_string(),
3939                kind: SymbolKind::Interface,
3940                range: node_range_with_decorators(&def_node, source, lang),
3941                signature: Some(extract_signature(source, &def_node)),
3942                scope_chain: vec![],
3943                exported: true,
3944                parent: None,
3945            });
3946        }
3947
3948        // Function — Method when inside a contract/library/interface, Function otherwise
3949        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
3950            let scope_chain = solidity_scope_chain(&def_node, source);
3951            let kind = if scope_chain.is_empty() {
3952                SymbolKind::Function
3953            } else {
3954                SymbolKind::Method
3955            };
3956            symbols.push(Symbol {
3957                name: node_text(source, &name_node).to_string(),
3958                kind,
3959                range: node_range_with_decorators(&def_node, source, lang),
3960                signature: Some(extract_signature(source, &def_node)),
3961                parent: scope_chain.last().cloned(),
3962                scope_chain,
3963                exported: true,
3964            });
3965        }
3966
3967        // Modifier — always inside a contract/library/interface, treat as Method
3968        if let (Some(name_node), Some(def_node)) = (modifier_name_node, modifier_def_node) {
3969            let scope_chain = solidity_scope_chain(&def_node, source);
3970            symbols.push(Symbol {
3971                name: node_text(source, &name_node).to_string(),
3972                kind: SymbolKind::Method,
3973                range: node_range_with_decorators(&def_node, source, lang),
3974                signature: Some(extract_signature(source, &def_node)),
3975                parent: scope_chain.last().cloned(),
3976                scope_chain,
3977                exported: true,
3978            });
3979        }
3980
3981        // Constructor — synthetic name "constructor", parent is the enclosing contract
3982        if let Some(def_node) = constructor_def_node {
3983            let scope_chain = solidity_scope_chain(&def_node, source);
3984            symbols.push(Symbol {
3985                name: "constructor".to_string(),
3986                kind: SymbolKind::Method,
3987                range: node_range_with_decorators(&def_node, source, lang),
3988                signature: Some(extract_signature(source, &def_node)),
3989                parent: scope_chain.last().cloned(),
3990                scope_chain,
3991                exported: true,
3992            });
3993        }
3994
3995        // receive() / fallback() — synthetic names, parent is the enclosing contract
3996        if let Some(def_node) = fallback_receive_def_node {
3997            let scope_chain = solidity_scope_chain(&def_node, source);
3998            let signature = extract_signature(source, &def_node);
3999            let name = if signature.trim_start().starts_with("receive") {
4000                "receive"
4001            } else {
4002                "fallback"
4003            };
4004            symbols.push(Symbol {
4005                name: name.to_string(),
4006                kind: SymbolKind::Method,
4007                range: node_range_with_decorators(&def_node, source, lang),
4008                signature: Some(signature),
4009                parent: scope_chain.last().cloned(),
4010                scope_chain,
4011                exported: true,
4012            });
4013        }
4014
4015        // Event
4016        if let (Some(name_node), Some(def_node)) = (event_name_node, event_def_node) {
4017            let scope_chain = solidity_scope_chain(&def_node, source);
4018            symbols.push(Symbol {
4019                name: node_text(source, &name_node).to_string(),
4020                kind: SymbolKind::Function,
4021                range: node_range_with_decorators(&def_node, source, lang),
4022                signature: Some(extract_signature(source, &def_node)),
4023                parent: scope_chain.last().cloned(),
4024                scope_chain,
4025                exported: true,
4026            });
4027        }
4028
4029        // Error (custom error declaration)
4030        if let (Some(name_node), Some(def_node)) = (error_name_node, error_def_node) {
4031            let scope_chain = solidity_scope_chain(&def_node, source);
4032            symbols.push(Symbol {
4033                name: node_text(source, &name_node).to_string(),
4034                kind: SymbolKind::TypeAlias,
4035                range: node_range_with_decorators(&def_node, source, lang),
4036                signature: Some(extract_signature(source, &def_node)),
4037                parent: scope_chain.last().cloned(),
4038                scope_chain,
4039                exported: true,
4040            });
4041        }
4042
4043        // Struct
4044        if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
4045            let scope_chain = solidity_scope_chain(&def_node, source);
4046            symbols.push(Symbol {
4047                name: node_text(source, &name_node).to_string(),
4048                kind: SymbolKind::Struct,
4049                range: node_range_with_decorators(&def_node, source, lang),
4050                signature: Some(extract_signature(source, &def_node)),
4051                parent: scope_chain.last().cloned(),
4052                scope_chain,
4053                exported: true,
4054            });
4055        }
4056
4057        // Enum
4058        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
4059            let scope_chain = solidity_scope_chain(&def_node, source);
4060            symbols.push(Symbol {
4061                name: node_text(source, &name_node).to_string(),
4062                kind: SymbolKind::Enum,
4063                range: node_range_with_decorators(&def_node, source, lang),
4064                signature: Some(extract_signature(source, &def_node)),
4065                parent: scope_chain.last().cloned(),
4066                scope_chain,
4067                exported: true,
4068            });
4069        }
4070
4071        // State variable
4072        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
4073            let scope_chain = solidity_scope_chain(&def_node, source);
4074            symbols.push(Symbol {
4075                name: node_text(source, &name_node).to_string(),
4076                kind: SymbolKind::Variable,
4077                range: node_range_with_decorators(&def_node, source, lang),
4078                signature: Some(extract_signature(source, &def_node)),
4079                parent: scope_chain.last().cloned(),
4080                scope_chain,
4081                exported: true,
4082            });
4083        }
4084    }
4085
4086    dedup_symbols(&mut symbols);
4087    Ok(symbols)
4088}
4089
4090fn extract_json_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
4091    let Some(value) = root.named_child(0) else {
4092        return Ok(Vec::new());
4093    };
4094
4095    if value.kind() != "object" {
4096        return Ok(Vec::new());
4097    }
4098
4099    let mut symbols = Vec::new();
4100    let mut cursor = value.walk();
4101    for child in value.named_children(&mut cursor) {
4102        if child.kind() != "pair" {
4103            continue;
4104        }
4105        let Some(key_node) = child.child_by_field_name("key") else {
4106            continue;
4107        };
4108        let name = node_text(source, &key_node).trim_matches('"').to_string();
4109        if name.is_empty() {
4110            continue;
4111        }
4112        symbols.push(Symbol {
4113            name,
4114            kind: SymbolKind::Variable,
4115            range: node_range_with_decorators(&child, source, LangId::Json),
4116            signature: None,
4117            scope_chain: vec![],
4118            exported: false,
4119            parent: None,
4120        });
4121    }
4122
4123    Ok(symbols)
4124}
4125
4126/// Read a YAML scalar/key node as trimmed text, stripping surrounding quotes.
4127fn yaml_scalar_text(source: &str, node: &Node) -> String {
4128    node_text(source, node)
4129        .trim()
4130        .trim_matches('"')
4131        .trim_matches('\'')
4132        .to_string()
4133}
4134
4135/// Find the mapping that is the direct payload of `node`, descending only
4136/// through wrapper nodes (`document`, `block_node`, `flow_node`) rather than
4137/// arbitrary nested mappings. This keeps detection anchored to the document's
4138/// top-level mapping instead of latching onto a mapping buried inside a
4139/// sequence or nested value.
4140fn yaml_find_mapping<'a>(node: &Node<'a>) -> Option<Node<'a>> {
4141    let mut current = *node;
4142    loop {
4143        match current.kind() {
4144            "block_mapping" | "flow_mapping" => return Some(current),
4145            "document" | "block_node" | "flow_node" => {
4146                let mut cursor = current.walk();
4147                let next = current
4148                    .named_children(&mut cursor)
4149                    .find(|child| !matches!(child.kind(), "tag" | "anchor"));
4150                match next {
4151                    Some(inner) => current = inner,
4152                    None => return None,
4153                }
4154            }
4155            _ => return None,
4156        }
4157    }
4158}
4159
4160/// Look up a top-level key in a YAML mapping and return its value node.
4161fn yaml_get<'a>(mapping: &Node<'a>, source: &str, key: &str) -> Option<Node<'a>> {
4162    let mut cursor = mapping.walk();
4163    for pair in mapping.named_children(&mut cursor) {
4164        if pair.kind() != "block_mapping_pair" && pair.kind() != "flow_pair" {
4165            continue;
4166        }
4167        let Some(key_node) = pair.child_by_field_name("key") else {
4168            continue;
4169        };
4170        if yaml_scalar_text(source, &key_node) == key {
4171            return pair.child_by_field_name("value");
4172        }
4173    }
4174    None
4175}
4176
4177/// Flatten a YAML value node to clean text for embed_text. Scalars return their
4178/// trimmed text; block/flow sequences are joined as comma-separated scalar items
4179/// (so `verbs: [get, list, watch]` becomes `get,list,watch` instead of raw
4180/// multi-line `- get\n- list` text). Nested mappings are ignored here.
4181fn yaml_unwrap<'a>(node: &Node<'a>) -> Node<'a> {
4182    let mut current = *node;
4183    while current.kind() == "block_node" || current.kind() == "flow_node" {
4184        let mut cursor = current.walk();
4185        let next = current
4186            .named_children(&mut cursor)
4187            .find(|child| !matches!(child.kind(), "tag" | "anchor"));
4188        match next {
4189            Some(inner) => current = inner,
4190            None => break,
4191        }
4192    }
4193    current
4194}
4195
4196fn yaml_flatten_value(source: &str, node: &Node) -> String {
4197    let node = &yaml_unwrap(node);
4198    match node.kind() {
4199        "block_sequence" | "flow_sequence" => {
4200            let mut items = Vec::new();
4201            let mut cursor = node.walk();
4202            for child in node.named_children(&mut cursor) {
4203                // block_sequence_item wraps a value; flow_sequence holds nodes directly.
4204                let value = if child.kind() == "block_sequence_item" {
4205                    child.named_child(0)
4206                } else {
4207                    Some(child)
4208                };
4209                if let Some(v) = value {
4210                    let v = yaml_unwrap(&v);
4211                    // Only flatten scalar leaves; skip mappings (handled elsewhere).
4212                    if v.kind() != "block_mapping" && v.kind() != "flow_mapping" {
4213                        let text = yaml_scalar_text(source, &v);
4214                        if !text.is_empty() {
4215                            items.push(text);
4216                        }
4217                    }
4218                }
4219            }
4220            items.join(",")
4221        }
4222        // Mapping values (e.g. container `resources:` block) are not flattened
4223        // here — their high-signal leaves (cpu/memory) are collected separately.
4224        // Returning empty avoids dumping raw multi-line YAML into embed_text.
4225        "block_mapping" | "flow_mapping" => String::new(),
4226        _ => yaml_scalar_text(source, node),
4227    }
4228}
4229
4230/// Recursively collect `key=value` pairs for the given keys (e.g. image, cpu,
4231/// memory, verbs) to enrich embed_text for semantic search. Sequence values are
4232/// flattened to comma-joined scalars. Capped to avoid runaway output.
4233fn yaml_collect_values(
4234    source: &str,
4235    node: &Node,
4236    keys: &[&str],
4237    out: &mut Vec<String>,
4238    cap: usize,
4239) {
4240    if out.len() >= cap {
4241        return;
4242    }
4243    let mut cursor = node.walk();
4244    for child in node.named_children(&mut cursor) {
4245        if child.kind() == "block_mapping_pair" || child.kind() == "flow_pair" {
4246            if let Some(key_node) = child.child_by_field_name("key") {
4247                let key_text = yaml_scalar_text(source, &key_node);
4248                if keys.contains(&key_text.as_str()) {
4249                    if let Some(value_node) = child.child_by_field_name("value") {
4250                        let value_text = yaml_flatten_value(source, &value_node);
4251                        if !value_text.is_empty() && out.len() < cap {
4252                            out.push(format!("{}={}", key_text, value_text));
4253                        }
4254                    }
4255                }
4256            }
4257        }
4258        yaml_collect_values(source, &child, keys, out, cap);
4259    }
4260}
4261
4262/// Collect the `name:` field from every item of a `<parent_key>:` sequence of
4263/// mappings. The bare `name` key is too generic to match globally (it collides
4264/// with metadata/container names), so these are gathered by parent key and
4265/// emitted as `<label>=A,B,...`. Handles k8s `env: [{name,value}]` and Argo
4266/// Workflow `templates: [{name, ...}]`. Capped.
4267fn yaml_collect_named_items(
4268    source: &str,
4269    node: &Node,
4270    parent_key: &str,
4271    label: &str,
4272    out: &mut Vec<String>,
4273    cap: usize,
4274) {
4275    let mut cursor = node.walk();
4276    for child in node.named_children(&mut cursor) {
4277        if (child.kind() == "block_mapping_pair" || child.kind() == "flow_pair")
4278            && child
4279                .child_by_field_name("key")
4280                .map(|k| yaml_scalar_text(source, &k))
4281                .as_deref()
4282                == Some(parent_key)
4283        {
4284            if let Some(seq) = child.child_by_field_name("value") {
4285                let seq = yaml_unwrap(&seq);
4286                let mut names = Vec::new();
4287                let mut seq_cursor = seq.walk();
4288                for item in seq.named_children(&mut seq_cursor) {
4289                    let value = if item.kind() == "block_sequence_item" {
4290                        item.named_child(0)
4291                    } else {
4292                        Some(item)
4293                    };
4294                    if let Some(v) = value {
4295                        if let Some(map) = yaml_find_mapping(&v) {
4296                            if let Some(name_node) = yaml_get(&map, source, "name") {
4297                                let n = yaml_scalar_text(source, &name_node);
4298                                if !n.is_empty() && names.len() < 16 {
4299                                    names.push(n);
4300                                }
4301                            }
4302                        }
4303                    }
4304                }
4305                if !names.is_empty() && out.len() < cap {
4306                    out.push(format!("{}={}", label, names.join(",")));
4307                }
4308            }
4309        }
4310        yaml_collect_named_items(source, &child, parent_key, label, out, cap);
4311    }
4312}
4313
4314/// Tier 1: if a document is a Kubernetes resource (has both apiVersion + kind),
4315/// emit one rich symbol named `<ns>/<Kind>/<name>` with enriched signature.
4316/// Generalizes to arbitrary CRDs since apiVersion+kind is the CRD contract.
4317fn yaml_k8s_resource_symbol(source: &str, doc: &Node, mapping: &Node) -> Option<Symbol> {
4318    let api_version =
4319        yaml_get(mapping, source, "apiVersion").map(|n| yaml_scalar_text(source, &n))?;
4320    let kind = yaml_get(mapping, source, "kind").map(|n| yaml_scalar_text(source, &n))?;
4321    if api_version.is_empty() || kind.is_empty() {
4322        return None;
4323    }
4324
4325    let (name, namespace) = match yaml_get(mapping, source, "metadata") {
4326        Some(meta) => match yaml_find_mapping(&meta) {
4327            Some(meta_map) => {
4328                // Prefer `name`; fall back to `generateName` (common in Argo
4329                // Workflows submitted without a fixed name) so the symbol still
4330                // carries an identifier instead of collapsing to bare <Kind>.
4331                let name = yaml_get(&meta_map, source, "name")
4332                    .map(|n| yaml_scalar_text(source, &n))
4333                    .filter(|s| !s.is_empty())
4334                    .or_else(|| {
4335                        yaml_get(&meta_map, source, "generateName")
4336                            .map(|n| yaml_scalar_text(source, &n))
4337                            .filter(|s| !s.is_empty())
4338                    });
4339                (
4340                    name,
4341                    yaml_get(&meta_map, source, "namespace").map(|n| yaml_scalar_text(source, &n)),
4342                )
4343            }
4344            None => (None, None),
4345        },
4346        None => (None, None),
4347    };
4348
4349    let res_name = name.clone().filter(|s| !s.is_empty());
4350    let sym_name = match (&namespace, &res_name) {
4351        (Some(ns), Some(n)) if !ns.is_empty() => format!("{}/{}/{}", ns, kind, n),
4352        (_, Some(n)) => format!("{}/{}", kind, n),
4353        _ => kind.clone(),
4354    };
4355
4356    let mut sig = format!("apiVersion={} kind={}", api_version, kind);
4357    if let Some(ns) = namespace.as_ref().filter(|s| !s.is_empty()) {
4358        sig.push_str(&format!(" namespace={}", ns));
4359    }
4360    if let Some(n) = res_name.as_ref() {
4361        sig.push_str(&format!(" name={}", n));
4362    }
4363    // Enrich with high-signal spec fields so intent queries match. Covers
4364    // containers (image/ports), resource limits/requests (cpu/memory), RBAC
4365    // rules (verbs/resources/apiGroups), and storage (volumeMounts mountPath).
4366    let mut extras = Vec::new();
4367    yaml_collect_values(
4368        source,
4369        mapping,
4370        &[
4371            "image",
4372            "containerPort",
4373            "port",
4374            "targetPort",
4375            "cpu",
4376            "memory",
4377            "storage",
4378            "verbs",
4379            "resources",
4380            "apiGroups",
4381            "mountPath",
4382            "replicas",
4383            // Argo Workflow high-signal scalars.
4384            "entrypoint",
4385            "command",
4386            "args",
4387            "schedule",
4388        ],
4389        &mut extras,
4390        24,
4391    );
4392    // Sequence-of-mappings whose `name` key is ambiguous: collect by parent key.
4393    // k8s env vars and Argo Workflow templates both use the `{name: ...}` shape.
4394    yaml_collect_named_items(source, mapping, "env", "env", &mut extras, 24);
4395    yaml_collect_named_items(source, mapping, "templates", "templates", &mut extras, 24);
4396    if !extras.is_empty() {
4397        sig.push(' ');
4398        sig.push_str(&extras.join(" "));
4399    }
4400
4401    Some(Symbol {
4402        name: sym_name,
4403        kind: SymbolKind::Class,
4404        range: node_range(doc),
4405        signature: Some(sig),
4406        scope_chain: vec![],
4407        exported: true,
4408        parent: None,
4409    })
4410}
4411
4412/// Tier 2: generic YAML — emit top-level mapping keys as Variable symbols
4413/// (docker-compose services, CI jobs, Helm values.yaml, etc.).
4414fn yaml_generic_keys(source: &str, mapping: &Node, symbols: &mut Vec<Symbol>) {
4415    let mut cursor = mapping.walk();
4416    for pair in mapping.named_children(&mut cursor) {
4417        if pair.kind() != "block_mapping_pair" && pair.kind() != "flow_pair" {
4418            continue;
4419        }
4420        let Some(key_node) = pair.child_by_field_name("key") else {
4421            continue;
4422        };
4423        let name = yaml_scalar_text(source, &key_node);
4424        if name.is_empty() {
4425            continue;
4426        }
4427        symbols.push(Symbol {
4428            name,
4429            kind: SymbolKind::Variable,
4430            range: node_range(&pair),
4431            signature: None,
4432            scope_chain: vec![],
4433            exported: false,
4434            parent: None,
4435        });
4436    }
4437}
4438
4439/// Extract symbols from a YAML stream. Handles multi-document (`---`) streams:
4440/// each document becomes a Kubernetes resource symbol (Tier 1) when it carries
4441/// apiVersion+kind, otherwise its top-level keys are emitted (Tier 2). Helm/Go
4442/// templated YAML that yields no parseable mapping degrades gracefully (the
4443/// document is skipped rather than failing the whole file).
4444fn extract_yaml_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
4445    let mut symbols = Vec::new();
4446    let mut cursor = root.walk();
4447    for doc in root.named_children(&mut cursor) {
4448        if doc.kind() != "document" {
4449            continue;
4450        }
4451        let Some(mapping) = yaml_find_mapping(&doc) else {
4452            continue;
4453        };
4454        if let Some(symbol) = yaml_k8s_resource_symbol(source, &doc, &mapping) {
4455            symbols.push(symbol);
4456        } else {
4457            yaml_generic_keys(source, &mapping, &mut symbols);
4458        }
4459    }
4460    Ok(symbols)
4461}
4462
4463fn scala_scope_chain(node: &Node, source: &str) -> Vec<String> {
4464    let mut chain = Vec::new();
4465    let mut current = node.parent();
4466
4467    while let Some(parent) = current {
4468        match parent.kind() {
4469            "class_definition" | "object_definition" | "enum_definition" | "trait_definition" => {
4470                if let Some(name_node) = parent.child_by_field_name("name") {
4471                    chain.push(node_text(source, &name_node).to_string());
4472                }
4473            }
4474            _ => {}
4475        }
4476        current = parent.parent();
4477    }
4478
4479    chain.reverse();
4480    chain
4481}
4482
4483fn extract_scala_symbols(
4484    source: &str,
4485    root: &Node,
4486    query: &Query,
4487) -> Result<Vec<Symbol>, AftError> {
4488    let lang = LangId::Scala;
4489    let capture_names = query.capture_names();
4490
4491    let mut symbols = Vec::new();
4492    let mut cursor = QueryCursor::new();
4493    let mut matches = cursor.matches(query, *root, source.as_bytes());
4494
4495    while let Some(m) = {
4496        matches.advance();
4497        matches.get()
4498    } {
4499        let mut class_name_node = None;
4500        let mut class_def_node = None;
4501        let mut object_name_node = None;
4502        let mut object_def_node = None;
4503        let mut enum_name_node = None;
4504        let mut enum_def_node = None;
4505        let mut trait_name_node = None;
4506        let mut trait_def_node = None;
4507        let mut fn_name_node = None;
4508        let mut fn_def_node = None;
4509        let mut val_name_node = None;
4510        let mut val_def_node = None;
4511        let mut var_name_node = None;
4512        let mut var_def_node = None;
4513        let mut type_name_node = None;
4514        let mut type_def_node = None;
4515
4516        for cap in m.captures {
4517            let Some(&name) = capture_names.get(cap.index as usize) else {
4518                continue;
4519            };
4520            match name {
4521                "class.name" => class_name_node = Some(cap.node),
4522                "class.def" => class_def_node = Some(cap.node),
4523                "object.name" => object_name_node = Some(cap.node),
4524                "object.def" => object_def_node = Some(cap.node),
4525                "enum.name" => enum_name_node = Some(cap.node),
4526                "enum.def" => enum_def_node = Some(cap.node),
4527                "trait.name" => trait_name_node = Some(cap.node),
4528                "trait.def" => trait_def_node = Some(cap.node),
4529                "fn.name" => fn_name_node = Some(cap.node),
4530                "fn.def" => fn_def_node = Some(cap.node),
4531                "val.name" => val_name_node = Some(cap.node),
4532                "val.def" => val_def_node = Some(cap.node),
4533                "var.name" => var_name_node = Some(cap.node),
4534                "var.def" => var_def_node = Some(cap.node),
4535                "given.name" => val_name_node = Some(cap.node),
4536                "given.def" => val_def_node = Some(cap.node),
4537                "type.name" => type_name_node = Some(cap.node),
4538                "type.def" => type_def_node = Some(cap.node),
4539                _ => {}
4540            }
4541        }
4542
4543        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
4544            symbols.push(Symbol {
4545                name: node_text(source, &name_node).to_string(),
4546                kind: SymbolKind::Class,
4547                range: node_range_with_decorators(&def_node, source, lang),
4548                signature: Some(extract_signature(source, &def_node)),
4549                scope_chain: scala_scope_chain(&def_node, source),
4550                exported: true,
4551                parent: scala_scope_chain(&def_node, source).last().cloned(),
4552            });
4553        }
4554
4555        if let (Some(name_node), Some(def_node)) = (object_name_node, object_def_node) {
4556            symbols.push(Symbol {
4557                name: node_text(source, &name_node).to_string(),
4558                kind: SymbolKind::Class,
4559                range: node_range_with_decorators(&def_node, source, lang),
4560                signature: Some(extract_signature(source, &def_node)),
4561                scope_chain: scala_scope_chain(&def_node, source),
4562                exported: true,
4563                parent: scala_scope_chain(&def_node, source).last().cloned(),
4564            });
4565        }
4566
4567        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
4568            symbols.push(Symbol {
4569                name: node_text(source, &name_node).to_string(),
4570                kind: SymbolKind::Enum,
4571                range: node_range_with_decorators(&def_node, source, lang),
4572                signature: Some(extract_signature(source, &def_node)),
4573                scope_chain: scala_scope_chain(&def_node, source),
4574                exported: true,
4575                parent: scala_scope_chain(&def_node, source).last().cloned(),
4576            });
4577        }
4578
4579        if let (Some(name_node), Some(def_node)) = (trait_name_node, trait_def_node) {
4580            symbols.push(Symbol {
4581                name: node_text(source, &name_node).to_string(),
4582                kind: SymbolKind::Interface,
4583                range: node_range_with_decorators(&def_node, source, lang),
4584                signature: Some(extract_signature(source, &def_node)),
4585                scope_chain: scala_scope_chain(&def_node, source),
4586                exported: true,
4587                parent: scala_scope_chain(&def_node, source).last().cloned(),
4588            });
4589        }
4590
4591        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
4592            let scope_chain = scala_scope_chain(&def_node, source);
4593            let kind = if scope_chain.is_empty() {
4594                SymbolKind::Function
4595            } else {
4596                SymbolKind::Method
4597            };
4598            symbols.push(Symbol {
4599                name: node_text(source, &name_node).to_string(),
4600                kind,
4601                range: node_range_with_decorators(&def_node, source, lang),
4602                signature: Some(extract_signature(source, &def_node)),
4603                parent: scope_chain.last().cloned(),
4604                scope_chain,
4605                exported: true,
4606            });
4607        }
4608
4609        if let (Some(name_node), Some(def_node)) = (val_name_node, val_def_node) {
4610            let scope_chain = scala_scope_chain(&def_node, source);
4611            symbols.push(Symbol {
4612                name: node_text(source, &name_node).to_string(),
4613                kind: SymbolKind::Variable,
4614                range: node_range_with_decorators(&def_node, source, lang),
4615                signature: Some(extract_signature(source, &def_node)),
4616                parent: scope_chain.last().cloned(),
4617                scope_chain,
4618                exported: true,
4619            });
4620        }
4621
4622        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
4623            let scope_chain = scala_scope_chain(&def_node, source);
4624            symbols.push(Symbol {
4625                name: node_text(source, &name_node).to_string(),
4626                kind: SymbolKind::Variable,
4627                range: node_range_with_decorators(&def_node, source, lang),
4628                signature: Some(extract_signature(source, &def_node)),
4629                parent: scope_chain.last().cloned(),
4630                scope_chain,
4631                exported: true,
4632            });
4633        }
4634
4635        if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
4636            let scope_chain = scala_scope_chain(&def_node, source);
4637            symbols.push(Symbol {
4638                name: node_text(source, &name_node).to_string(),
4639                kind: SymbolKind::TypeAlias,
4640                range: node_range_with_decorators(&def_node, source, lang),
4641                signature: Some(extract_signature(source, &def_node)),
4642                parent: scope_chain.last().cloned(),
4643                scope_chain,
4644                exported: true,
4645            });
4646        }
4647    }
4648
4649    dedup_symbols(&mut symbols);
4650    Ok(symbols)
4651}
4652
4653fn child_text_by_field_or_kind(
4654    node: &Node,
4655    source: &str,
4656    field_name: &str,
4657    kinds: &[&str],
4658) -> Option<String> {
4659    if let Some(name_node) = node.child_by_field_name(field_name) {
4660        return Some(node_text(source, &name_node).to_string());
4661    }
4662
4663    let mut cursor = node.walk();
4664    if !cursor.goto_first_child() {
4665        return None;
4666    }
4667
4668    loop {
4669        let child = cursor.node();
4670        if kinds.contains(&child.kind()) {
4671            return Some(node_text(source, &child).to_string());
4672        }
4673        if !cursor.goto_next_sibling() {
4674            break;
4675        }
4676    }
4677
4678    None
4679}
4680
4681fn push_captured_symbol(
4682    symbols: &mut Vec<Symbol>,
4683    source: &str,
4684    lang: LangId,
4685    name_node: Node,
4686    def_node: Node,
4687    kind: SymbolKind,
4688    scope_chain: Vec<String>,
4689    exported: bool,
4690) {
4691    symbols.push(Symbol {
4692        name: node_text(source, &name_node).to_string(),
4693        kind,
4694        range: node_range_with_decorators(&def_node, source, lang),
4695        signature: Some(extract_signature(source, &def_node)),
4696        parent: scope_chain.last().cloned(),
4697        scope_chain,
4698        exported,
4699    });
4700}
4701
4702fn java_scope_chain(node: &Node, source: &str) -> Vec<String> {
4703    let mut chain = Vec::new();
4704    let mut current = node.parent();
4705
4706    while let Some(parent) = current {
4707        if matches!(
4708            parent.kind(),
4709            "class_declaration"
4710                | "interface_declaration"
4711                | "annotation_type_declaration"
4712                | "enum_declaration"
4713                | "record_declaration"
4714        ) {
4715            if let Some(name_node) = parent.child_by_field_name("name") {
4716                chain.push(node_text(source, &name_node).to_string());
4717            }
4718        }
4719        current = parent.parent();
4720    }
4721
4722    chain.reverse();
4723    chain
4724}
4725
4726fn extract_java_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
4727    let lang = LangId::Java;
4728    let capture_names = query.capture_names();
4729    let mut symbols = Vec::new();
4730    let mut cursor = QueryCursor::new();
4731    let mut matches = cursor.matches(query, *root, source.as_bytes());
4732
4733    while let Some(m) = {
4734        matches.advance();
4735        matches.get()
4736    } {
4737        let mut class_name_node = None;
4738        let mut class_def_node = None;
4739        let mut interface_name_node = None;
4740        let mut interface_def_node = None;
4741        let mut enum_name_node = None;
4742        let mut enum_def_node = None;
4743        let mut struct_name_node = None;
4744        let mut struct_def_node = None;
4745        let mut fn_name_node = None;
4746        let mut fn_def_node = None;
4747        let mut var_name_node = None;
4748        let mut var_def_node = None;
4749
4750        for cap in m.captures {
4751            let Some(&name) = capture_names.get(cap.index as usize) else {
4752                continue;
4753            };
4754            match name {
4755                "class.name" => class_name_node = Some(cap.node),
4756                "class.def" => class_def_node = Some(cap.node),
4757                "interface.name" => interface_name_node = Some(cap.node),
4758                "interface.def" => interface_def_node = Some(cap.node),
4759                "enum.name" => enum_name_node = Some(cap.node),
4760                "enum.def" => enum_def_node = Some(cap.node),
4761                "struct.name" => struct_name_node = Some(cap.node),
4762                "struct.def" => struct_def_node = Some(cap.node),
4763                "fn.name" => fn_name_node = Some(cap.node),
4764                "fn.def" => fn_def_node = Some(cap.node),
4765                "var.name" => var_name_node = Some(cap.node),
4766                "var.def" => var_def_node = Some(cap.node),
4767                _ => {}
4768            }
4769        }
4770
4771        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
4772            push_captured_symbol(
4773                &mut symbols,
4774                source,
4775                lang,
4776                name_node,
4777                def_node,
4778                SymbolKind::Class,
4779                java_scope_chain(&def_node, source),
4780                true,
4781            );
4782        }
4783        if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
4784            push_captured_symbol(
4785                &mut symbols,
4786                source,
4787                lang,
4788                name_node,
4789                def_node,
4790                SymbolKind::Interface,
4791                java_scope_chain(&def_node, source),
4792                true,
4793            );
4794        }
4795        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
4796            push_captured_symbol(
4797                &mut symbols,
4798                source,
4799                lang,
4800                name_node,
4801                def_node,
4802                SymbolKind::Enum,
4803                java_scope_chain(&def_node, source),
4804                true,
4805            );
4806        }
4807        if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
4808            push_captured_symbol(
4809                &mut symbols,
4810                source,
4811                lang,
4812                name_node,
4813                def_node,
4814                SymbolKind::Struct,
4815                java_scope_chain(&def_node, source),
4816                true,
4817            );
4818        }
4819        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
4820            let scope_chain = java_scope_chain(&def_node, source);
4821            let kind = if scope_chain.is_empty() {
4822                SymbolKind::Function
4823            } else {
4824                SymbolKind::Method
4825            };
4826            push_captured_symbol(
4827                &mut symbols,
4828                source,
4829                lang,
4830                name_node,
4831                def_node,
4832                kind,
4833                scope_chain,
4834                true,
4835            );
4836        }
4837        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
4838            push_captured_symbol(
4839                &mut symbols,
4840                source,
4841                lang,
4842                name_node,
4843                def_node,
4844                SymbolKind::Variable,
4845                java_scope_chain(&def_node, source),
4846                true,
4847            );
4848        }
4849    }
4850
4851    dedup_symbols(&mut symbols);
4852    Ok(symbols)
4853}
4854
4855fn ruby_scope_chain(node: &Node, source: &str) -> Vec<String> {
4856    let mut chain = Vec::new();
4857    let mut current = node.parent();
4858
4859    while let Some(parent) = current {
4860        if matches!(parent.kind(), "class" | "module") {
4861            if let Some(name_node) = parent.child_by_field_name("name") {
4862                chain.push(node_text(source, &name_node).to_string());
4863            }
4864        }
4865        current = parent.parent();
4866    }
4867
4868    chain.reverse();
4869    chain
4870}
4871
4872fn extract_ruby_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
4873    let lang = LangId::Ruby;
4874    let capture_names = query.capture_names();
4875    let mut symbols = Vec::new();
4876    let mut cursor = QueryCursor::new();
4877    let mut matches = cursor.matches(query, *root, source.as_bytes());
4878
4879    while let Some(m) = {
4880        matches.advance();
4881        matches.get()
4882    } {
4883        let mut module_name_node = None;
4884        let mut module_def_node = None;
4885        let mut class_name_node = None;
4886        let mut class_def_node = None;
4887        let mut fn_name_node = None;
4888        let mut fn_def_node = None;
4889        let mut var_name_node = None;
4890        let mut var_def_node = None;
4891
4892        for cap in m.captures {
4893            let Some(&name) = capture_names.get(cap.index as usize) else {
4894                continue;
4895            };
4896            match name {
4897                "module.name" => module_name_node = Some(cap.node),
4898                "module.def" => module_def_node = Some(cap.node),
4899                "class.name" => class_name_node = Some(cap.node),
4900                "class.def" => class_def_node = Some(cap.node),
4901                "fn.name" => fn_name_node = Some(cap.node),
4902                "fn.def" => fn_def_node = Some(cap.node),
4903                "var.name" => var_name_node = Some(cap.node),
4904                "var.def" => var_def_node = Some(cap.node),
4905                _ => {}
4906            }
4907        }
4908
4909        if let (Some(name_node), Some(def_node)) = (module_name_node, module_def_node) {
4910            push_captured_symbol(
4911                &mut symbols,
4912                source,
4913                lang,
4914                name_node,
4915                def_node,
4916                SymbolKind::Class,
4917                ruby_scope_chain(&def_node, source),
4918                true,
4919            );
4920        }
4921        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
4922            push_captured_symbol(
4923                &mut symbols,
4924                source,
4925                lang,
4926                name_node,
4927                def_node,
4928                SymbolKind::Class,
4929                ruby_scope_chain(&def_node, source),
4930                true,
4931            );
4932        }
4933        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
4934            let scope_chain = ruby_scope_chain(&def_node, source);
4935            let kind = if scope_chain.is_empty() {
4936                SymbolKind::Function
4937            } else {
4938                SymbolKind::Method
4939            };
4940            push_captured_symbol(
4941                &mut symbols,
4942                source,
4943                lang,
4944                name_node,
4945                def_node,
4946                kind,
4947                scope_chain,
4948                true,
4949            );
4950        }
4951        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
4952            push_captured_symbol(
4953                &mut symbols,
4954                source,
4955                lang,
4956                name_node,
4957                def_node,
4958                SymbolKind::Variable,
4959                ruby_scope_chain(&def_node, source),
4960                true,
4961            );
4962        }
4963    }
4964
4965    dedup_symbols(&mut symbols);
4966    Ok(symbols)
4967}
4968
4969fn kotlin_scope_chain(node: &Node, source: &str) -> Vec<String> {
4970    let mut chain = Vec::new();
4971    let mut current = node.parent();
4972
4973    while let Some(parent) = current {
4974        if matches!(parent.kind(), "class_declaration" | "object_declaration") {
4975            if let Some(name) =
4976                child_text_by_field_or_kind(&parent, source, "name", &["type_identifier"])
4977            {
4978                chain.push(name);
4979            }
4980        }
4981        current = parent.parent();
4982    }
4983
4984    chain.reverse();
4985    chain
4986}
4987
4988fn extract_kotlin_symbols(
4989    source: &str,
4990    root: &Node,
4991    query: &Query,
4992) -> Result<Vec<Symbol>, AftError> {
4993    let lang = LangId::Kotlin;
4994    let capture_names = query.capture_names();
4995    let mut symbols = Vec::new();
4996    let mut cursor = QueryCursor::new();
4997    let mut matches = cursor.matches(query, *root, source.as_bytes());
4998
4999    while let Some(m) = {
5000        matches.advance();
5001        matches.get()
5002    } {
5003        let mut class_name_node = None;
5004        let mut class_def_node = None;
5005        let mut object_name_node = None;
5006        let mut object_def_node = None;
5007        let mut fn_name_node = None;
5008        let mut fn_def_node = None;
5009        let mut var_name_node = None;
5010        let mut var_def_node = None;
5011        let mut type_name_node = None;
5012        let mut type_def_node = None;
5013
5014        for cap in m.captures {
5015            let Some(&name) = capture_names.get(cap.index as usize) else {
5016                continue;
5017            };
5018            match name {
5019                "class.name" => class_name_node = Some(cap.node),
5020                "class.def" => class_def_node = Some(cap.node),
5021                "object.name" => object_name_node = Some(cap.node),
5022                "object.def" => object_def_node = Some(cap.node),
5023                "fn.name" => fn_name_node = Some(cap.node),
5024                "fn.def" => fn_def_node = Some(cap.node),
5025                "var.name" => var_name_node = Some(cap.node),
5026                "var.def" => var_def_node = Some(cap.node),
5027                "type.name" => type_name_node = Some(cap.node),
5028                "type.def" => type_def_node = Some(cap.node),
5029                _ => {}
5030            }
5031        }
5032
5033        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
5034            push_captured_symbol(
5035                &mut symbols,
5036                source,
5037                lang,
5038                name_node,
5039                def_node,
5040                SymbolKind::Class,
5041                kotlin_scope_chain(&def_node, source),
5042                true,
5043            );
5044        }
5045        if let (Some(name_node), Some(def_node)) = (object_name_node, object_def_node) {
5046            push_captured_symbol(
5047                &mut symbols,
5048                source,
5049                lang,
5050                name_node,
5051                def_node,
5052                SymbolKind::Class,
5053                kotlin_scope_chain(&def_node, source),
5054                true,
5055            );
5056        }
5057        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
5058            let scope_chain = kotlin_scope_chain(&def_node, source);
5059            let kind = if scope_chain.is_empty() {
5060                SymbolKind::Function
5061            } else {
5062                SymbolKind::Method
5063            };
5064            push_captured_symbol(
5065                &mut symbols,
5066                source,
5067                lang,
5068                name_node,
5069                def_node,
5070                kind,
5071                scope_chain,
5072                true,
5073            );
5074        }
5075        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
5076            push_captured_symbol(
5077                &mut symbols,
5078                source,
5079                lang,
5080                name_node,
5081                def_node,
5082                SymbolKind::Variable,
5083                kotlin_scope_chain(&def_node, source),
5084                true,
5085            );
5086        }
5087        if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
5088            push_captured_symbol(
5089                &mut symbols,
5090                source,
5091                lang,
5092                name_node,
5093                def_node,
5094                SymbolKind::TypeAlias,
5095                kotlin_scope_chain(&def_node, source),
5096                true,
5097            );
5098        }
5099    }
5100
5101    dedup_symbols(&mut symbols);
5102    Ok(symbols)
5103}
5104
5105fn swift_scope_chain(node: &Node, source: &str) -> Vec<String> {
5106    let mut chain = Vec::new();
5107    let mut current = node.parent();
5108
5109    while let Some(parent) = current {
5110        if matches!(parent.kind(), "class_declaration" | "protocol_declaration") {
5111            if let Some(name_node) = parent.child_by_field_name("name") {
5112                chain.push(node_text(source, &name_node).to_string());
5113            }
5114        }
5115        current = parent.parent();
5116    }
5117
5118    chain.reverse();
5119    chain
5120}
5121
5122fn swift_type_kind(source: &str, node: &Node) -> SymbolKind {
5123    match node
5124        .child_by_field_name("declaration_kind")
5125        .map(|kind_node| node_text(source, &kind_node))
5126    {
5127        Some("struct") => SymbolKind::Struct,
5128        Some("enum") => SymbolKind::Enum,
5129        _ => SymbolKind::Class,
5130    }
5131}
5132
5133fn extract_swift_symbols(
5134    source: &str,
5135    root: &Node,
5136    query: &Query,
5137) -> Result<Vec<Symbol>, AftError> {
5138    let lang = LangId::Swift;
5139    let capture_names = query.capture_names();
5140    let mut symbols = Vec::new();
5141    let mut cursor = QueryCursor::new();
5142    let mut matches = cursor.matches(query, *root, source.as_bytes());
5143
5144    while let Some(m) = {
5145        matches.advance();
5146        matches.get()
5147    } {
5148        let mut class_name_node = None;
5149        let mut class_def_node = None;
5150        let mut interface_name_node = None;
5151        let mut interface_def_node = None;
5152        let mut fn_name_node = None;
5153        let mut fn_def_node = None;
5154        let mut var_name_node = None;
5155        let mut var_def_node = None;
5156        let mut type_name_node = None;
5157        let mut type_def_node = None;
5158
5159        for cap in m.captures {
5160            let Some(&name) = capture_names.get(cap.index as usize) else {
5161                continue;
5162            };
5163            match name {
5164                "class.name" => class_name_node = Some(cap.node),
5165                "class.def" => class_def_node = Some(cap.node),
5166                "interface.name" => interface_name_node = Some(cap.node),
5167                "interface.def" => interface_def_node = Some(cap.node),
5168                "fn.name" => fn_name_node = Some(cap.node),
5169                "fn.def" => fn_def_node = Some(cap.node),
5170                "var.name" => var_name_node = Some(cap.node),
5171                "var.def" => var_def_node = Some(cap.node),
5172                "type.name" => type_name_node = Some(cap.node),
5173                "type.def" => type_def_node = Some(cap.node),
5174                _ => {}
5175            }
5176        }
5177
5178        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
5179            let kind = swift_type_kind(source, &def_node);
5180            push_captured_symbol(
5181                &mut symbols,
5182                source,
5183                lang,
5184                name_node,
5185                def_node,
5186                kind,
5187                swift_scope_chain(&def_node, source),
5188                true,
5189            );
5190        }
5191        if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
5192            push_captured_symbol(
5193                &mut symbols,
5194                source,
5195                lang,
5196                name_node,
5197                def_node,
5198                SymbolKind::Interface,
5199                swift_scope_chain(&def_node, source),
5200                true,
5201            );
5202        }
5203        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
5204            let scope_chain = swift_scope_chain(&def_node, source);
5205            let kind = if scope_chain.is_empty() {
5206                SymbolKind::Function
5207            } else {
5208                SymbolKind::Method
5209            };
5210            push_captured_symbol(
5211                &mut symbols,
5212                source,
5213                lang,
5214                name_node,
5215                def_node,
5216                kind,
5217                scope_chain,
5218                true,
5219            );
5220        }
5221        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
5222            push_captured_symbol(
5223                &mut symbols,
5224                source,
5225                lang,
5226                name_node,
5227                def_node,
5228                SymbolKind::Variable,
5229                swift_scope_chain(&def_node, source),
5230                true,
5231            );
5232        }
5233        if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
5234            push_captured_symbol(
5235                &mut symbols,
5236                source,
5237                lang,
5238                name_node,
5239                def_node,
5240                SymbolKind::TypeAlias,
5241                swift_scope_chain(&def_node, source),
5242                true,
5243            );
5244        }
5245    }
5246
5247    dedup_symbols(&mut symbols);
5248    Ok(symbols)
5249}
5250
5251fn php_scope_chain(node: &Node, source: &str) -> Vec<String> {
5252    let mut chain = Vec::new();
5253    let mut current = node.parent();
5254
5255    while let Some(parent) = current {
5256        match parent.kind() {
5257            "namespace_definition"
5258            | "class_declaration"
5259            | "interface_declaration"
5260            | "trait_declaration"
5261            | "enum_declaration" => {
5262                if let Some(name_node) = parent.child_by_field_name("name") {
5263                    chain.push(node_text(source, &name_node).to_string());
5264                }
5265            }
5266            _ => {}
5267        }
5268        current = parent.parent();
5269    }
5270
5271    chain.reverse();
5272    chain
5273}
5274
5275fn extract_scss_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
5276    let lang = LangId::Scss;
5277    let capture_names = query.capture_names();
5278    let mut symbols = Vec::new();
5279    let mut cursor = QueryCursor::new();
5280    let mut matches = cursor.matches(query, *root, source.as_bytes());
5281
5282    while let Some(m) = {
5283        matches.advance();
5284        matches.get()
5285    } {
5286        let mut mixin_name_node = None;
5287        let mut mixin_def_node = None;
5288        let mut fn_name_node = None;
5289        let mut fn_def_node = None;
5290        let mut var_name_node = None;
5291        let mut var_def_node = None;
5292        let mut selector_name_node = None;
5293        let mut selector_def_node = None;
5294
5295        for cap in m.captures {
5296            let Some(&name) = capture_names.get(cap.index as usize) else {
5297                continue;
5298            };
5299            match name {
5300                "mixin.name" => mixin_name_node = Some(cap.node),
5301                "mixin.def" => mixin_def_node = Some(cap.node),
5302                "fn.name" => fn_name_node = Some(cap.node),
5303                "fn.def" => fn_def_node = Some(cap.node),
5304                "var.name" => var_name_node = Some(cap.node),
5305                "var.def" => var_def_node = Some(cap.node),
5306                "selector.name" => selector_name_node = Some(cap.node),
5307                "selector.def" => selector_def_node = Some(cap.node),
5308                _ => {}
5309            }
5310        }
5311
5312        if let (Some(name_node), Some(def_node)) = (mixin_name_node, mixin_def_node) {
5313            push_captured_symbol(
5314                &mut symbols,
5315                source,
5316                lang,
5317                name_node,
5318                def_node,
5319                SymbolKind::Function,
5320                vec![],
5321                true,
5322            );
5323        }
5324        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
5325            push_captured_symbol(
5326                &mut symbols,
5327                source,
5328                lang,
5329                name_node,
5330                def_node,
5331                SymbolKind::Function,
5332                vec![],
5333                true,
5334            );
5335        }
5336        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
5337            if !node_text(source, &name_node).starts_with('$') {
5338                continue;
5339            }
5340            push_captured_symbol(
5341                &mut symbols,
5342                source,
5343                lang,
5344                name_node,
5345                def_node,
5346                SymbolKind::Variable,
5347                vec![],
5348                true,
5349            );
5350        }
5351        if let (Some(name_node), Some(def_node)) = (selector_name_node, selector_def_node) {
5352            push_captured_symbol(
5353                &mut symbols,
5354                source,
5355                lang,
5356                name_node,
5357                def_node,
5358                SymbolKind::Class,
5359                vec![],
5360                true,
5361            );
5362        }
5363    }
5364
5365    Ok(symbols)
5366}
5367
5368fn extract_php_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
5369    let lang = LangId::Php;
5370    let capture_names = query.capture_names();
5371    let mut symbols = Vec::new();
5372    let mut cursor = QueryCursor::new();
5373    let mut matches = cursor.matches(query, *root, source.as_bytes());
5374
5375    while let Some(m) = {
5376        matches.advance();
5377        matches.get()
5378    } {
5379        let mut namespace_name_node = None;
5380        let mut namespace_def_node = None;
5381        let mut class_name_node = None;
5382        let mut class_def_node = None;
5383        let mut interface_name_node = None;
5384        let mut interface_def_node = None;
5385        let mut trait_name_node = None;
5386        let mut trait_def_node = None;
5387        let mut enum_name_node = None;
5388        let mut enum_def_node = None;
5389        let mut fn_name_node = None;
5390        let mut fn_def_node = None;
5391        let mut var_name_node = None;
5392        let mut var_def_node = None;
5393
5394        for cap in m.captures {
5395            let Some(&name) = capture_names.get(cap.index as usize) else {
5396                continue;
5397            };
5398            match name {
5399                "namespace.name" => namespace_name_node = Some(cap.node),
5400                "namespace.def" => namespace_def_node = Some(cap.node),
5401                "class.name" => class_name_node = Some(cap.node),
5402                "class.def" => class_def_node = Some(cap.node),
5403                "interface.name" => interface_name_node = Some(cap.node),
5404                "interface.def" => interface_def_node = Some(cap.node),
5405                "trait.name" => trait_name_node = Some(cap.node),
5406                "trait.def" => trait_def_node = Some(cap.node),
5407                "enum.name" => enum_name_node = Some(cap.node),
5408                "enum.def" => enum_def_node = Some(cap.node),
5409                "fn.name" => fn_name_node = Some(cap.node),
5410                "fn.def" => fn_def_node = Some(cap.node),
5411                "var.name" => var_name_node = Some(cap.node),
5412                "var.def" => var_def_node = Some(cap.node),
5413                _ => {}
5414            }
5415        }
5416
5417        if let (Some(name_node), Some(def_node)) = (namespace_name_node, namespace_def_node) {
5418            push_captured_symbol(
5419                &mut symbols,
5420                source,
5421                lang,
5422                name_node,
5423                def_node,
5424                SymbolKind::Class,
5425                php_scope_chain(&def_node, source),
5426                true,
5427            );
5428        }
5429        if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
5430            push_captured_symbol(
5431                &mut symbols,
5432                source,
5433                lang,
5434                name_node,
5435                def_node,
5436                SymbolKind::Class,
5437                php_scope_chain(&def_node, source),
5438                true,
5439            );
5440        }
5441        if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
5442            push_captured_symbol(
5443                &mut symbols,
5444                source,
5445                lang,
5446                name_node,
5447                def_node,
5448                SymbolKind::Interface,
5449                php_scope_chain(&def_node, source),
5450                true,
5451            );
5452        }
5453        if let (Some(name_node), Some(def_node)) = (trait_name_node, trait_def_node) {
5454            push_captured_symbol(
5455                &mut symbols,
5456                source,
5457                lang,
5458                name_node,
5459                def_node,
5460                SymbolKind::Interface,
5461                php_scope_chain(&def_node, source),
5462                true,
5463            );
5464        }
5465        if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
5466            push_captured_symbol(
5467                &mut symbols,
5468                source,
5469                lang,
5470                name_node,
5471                def_node,
5472                SymbolKind::Enum,
5473                php_scope_chain(&def_node, source),
5474                true,
5475            );
5476        }
5477        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
5478            let scope_chain = php_scope_chain(&def_node, source);
5479            let kind = if scope_chain.is_empty() || def_node.kind() == "function_definition" {
5480                SymbolKind::Function
5481            } else {
5482                SymbolKind::Method
5483            };
5484            push_captured_symbol(
5485                &mut symbols,
5486                source,
5487                lang,
5488                name_node,
5489                def_node,
5490                kind,
5491                scope_chain,
5492                true,
5493            );
5494        }
5495        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
5496            push_captured_symbol(
5497                &mut symbols,
5498                source,
5499                lang,
5500                name_node,
5501                def_node,
5502                SymbolKind::Variable,
5503                php_scope_chain(&def_node, source),
5504                true,
5505            );
5506        }
5507    }
5508
5509    dedup_symbols(&mut symbols);
5510    Ok(symbols)
5511}
5512
5513fn lua_scope_chain(node: &Node, source: &str) -> Vec<String> {
5514    let mut chain = Vec::new();
5515
5516    if node.kind() == "function_declaration" {
5517        if let Some(name_node) = node.child_by_field_name("name") {
5518            match name_node.kind() {
5519                "dot_index_expression" | "method_index_expression" => {
5520                    if let Some(table_node) = name_node.child_by_field_name("table") {
5521                        chain.push(node_text(source, &table_node).to_string());
5522                    }
5523                }
5524                _ => {}
5525            }
5526        }
5527    }
5528
5529    chain
5530}
5531
5532fn extract_lua_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
5533    let lang = LangId::Lua;
5534    let capture_names = query.capture_names();
5535    let mut symbols = Vec::new();
5536    let mut cursor = QueryCursor::new();
5537    let mut matches = cursor.matches(query, *root, source.as_bytes());
5538
5539    while let Some(m) = {
5540        matches.advance();
5541        matches.get()
5542    } {
5543        let mut fn_name_node = None;
5544        let mut fn_def_node = None;
5545        let mut var_name_node = None;
5546        let mut var_def_node = None;
5547
5548        for cap in m.captures {
5549            let Some(&name) = capture_names.get(cap.index as usize) else {
5550                continue;
5551            };
5552            match name {
5553                "fn.name" => fn_name_node = Some(cap.node),
5554                "fn.def" => fn_def_node = Some(cap.node),
5555                "var.name" => var_name_node = Some(cap.node),
5556                "var.def" => var_def_node = Some(cap.node),
5557                _ => {}
5558            }
5559        }
5560
5561        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
5562            let scope_chain = lua_scope_chain(&def_node, source);
5563            let kind = if scope_chain.is_empty() {
5564                SymbolKind::Function
5565            } else {
5566                SymbolKind::Method
5567            };
5568            push_captured_symbol(
5569                &mut symbols,
5570                source,
5571                lang,
5572                name_node,
5573                def_node,
5574                kind,
5575                scope_chain,
5576                true,
5577            );
5578        }
5579        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
5580            push_captured_symbol(
5581                &mut symbols,
5582                source,
5583                lang,
5584                name_node,
5585                def_node,
5586                SymbolKind::Variable,
5587                vec![],
5588                true,
5589            );
5590        }
5591    }
5592
5593    dedup_symbols(&mut symbols);
5594    Ok(symbols)
5595}
5596
5597fn perl_package_name(source: &str, node: &Node) -> Option<String> {
5598    let mut cursor = node.walk();
5599    if !cursor.goto_first_child() {
5600        return None;
5601    }
5602
5603    loop {
5604        let child = cursor.node();
5605        if child.kind() == "package_name" {
5606            return Some(node_text(source, &child).to_string());
5607        }
5608        if !cursor.goto_next_sibling() {
5609            break;
5610        }
5611    }
5612
5613    None
5614}
5615
5616fn perl_scope_chain(node: &Node, source: &str) -> Vec<String> {
5617    let mut chain = Vec::new();
5618    let mut current = node.parent();
5619
5620    while let Some(parent) = current {
5621        if parent.kind() == "package_statement" {
5622            if let Some(name) = perl_package_name(source, &parent) {
5623                chain.push(name);
5624            }
5625        }
5626        current = parent.parent();
5627    }
5628
5629    if chain.is_empty() && node.kind() != "package_statement" {
5630        let mut sibling = node.prev_sibling();
5631        while let Some(prev) = sibling {
5632            if prev.kind() == "package_statement" {
5633                if let Some(name) = perl_package_name(source, &prev) {
5634                    chain.push(name);
5635                }
5636                break;
5637            }
5638            sibling = prev.prev_sibling();
5639        }
5640    }
5641
5642    chain.reverse();
5643    chain
5644}
5645
5646fn extract_perl_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
5647    let lang = LangId::Perl;
5648    let capture_names = query.capture_names();
5649    let mut symbols = Vec::new();
5650    let mut cursor = QueryCursor::new();
5651    let mut matches = cursor.matches(query, *root, source.as_bytes());
5652
5653    while let Some(m) = {
5654        matches.advance();
5655        matches.get()
5656    } {
5657        let mut package_name_node = None;
5658        let mut package_def_node = None;
5659        let mut fn_name_node = None;
5660        let mut fn_def_node = None;
5661        let mut var_name_node = None;
5662        let mut var_def_node = None;
5663
5664        for cap in m.captures {
5665            let Some(&name) = capture_names.get(cap.index as usize) else {
5666                continue;
5667            };
5668            match name {
5669                "package.name" => package_name_node = Some(cap.node),
5670                "package.def" => package_def_node = Some(cap.node),
5671                "fn.name" => fn_name_node = Some(cap.node),
5672                "fn.def" => fn_def_node = Some(cap.node),
5673                "var.name" => var_name_node = Some(cap.node),
5674                "var.def" => var_def_node = Some(cap.node),
5675                _ => {}
5676            }
5677        }
5678
5679        if let (Some(name_node), Some(def_node)) = (package_name_node, package_def_node) {
5680            push_captured_symbol(
5681                &mut symbols,
5682                source,
5683                lang,
5684                name_node,
5685                def_node,
5686                SymbolKind::Class,
5687                vec![],
5688                true,
5689            );
5690        }
5691        if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
5692            let scope_chain = perl_scope_chain(&def_node, source);
5693            let kind = if scope_chain.is_empty() {
5694                SymbolKind::Function
5695            } else {
5696                SymbolKind::Method
5697            };
5698            push_captured_symbol(
5699                &mut symbols,
5700                source,
5701                lang,
5702                name_node,
5703                def_node,
5704                kind,
5705                scope_chain,
5706                true,
5707            );
5708        }
5709        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
5710            push_captured_symbol(
5711                &mut symbols,
5712                source,
5713                lang,
5714                name_node,
5715                def_node,
5716                SymbolKind::Variable,
5717                perl_scope_chain(&def_node, source),
5718                true,
5719            );
5720        }
5721    }
5722
5723    dedup_symbols(&mut symbols);
5724    Ok(symbols)
5725}
5726
5727fn extract_vue_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
5728    let mut symbols = Vec::new();
5729    collect_vue_sections(source, root, &mut symbols, true);
5730    dedup_symbols(&mut symbols);
5731    Ok(symbols)
5732}
5733
5734fn collect_vue_sections(
5735    source: &str,
5736    node: &Node,
5737    symbols: &mut Vec<Symbol>,
5738    allow_sections: bool,
5739) {
5740    let mut cursor = node.walk();
5741    if !cursor.goto_first_child() {
5742        return;
5743    }
5744
5745    loop {
5746        let child = cursor.node();
5747        if let Some(section_name) = vue_section_name(&child) {
5748            if allow_sections {
5749                symbols.push(Symbol {
5750                    name: section_name.to_string(),
5751                    kind: SymbolKind::Heading,
5752                    range: node_range(&child),
5753                    signature: vue_opening_tag_signature(source, &child),
5754                    scope_chain: vec![],
5755                    exported: false,
5756                    parent: None,
5757                });
5758            }
5759        } else {
5760            collect_vue_sections(
5761                source,
5762                &child,
5763                symbols,
5764                allow_sections && child.kind() == "document",
5765            );
5766        }
5767
5768        if !cursor.goto_next_sibling() {
5769            break;
5770        }
5771    }
5772}
5773
5774fn vue_section_name(node: &Node) -> Option<&'static str> {
5775    match node.kind() {
5776        "template_element" => Some("template"),
5777        "script_element" => Some("script"),
5778        "style_element" => Some("style"),
5779        _ => None,
5780    }
5781}
5782
5783fn vue_opening_tag_signature(source: &str, node: &Node) -> Option<String> {
5784    find_child_by_kind(*node, "start_tag")
5785        .or_else(|| find_child_by_kind(*node, "script_start_tag"))
5786        .or_else(|| find_child_by_kind(*node, "style_start_tag"))
5787        .or_else(|| find_child_by_kind(*node, "template_start_tag"))
5788        .map(|tag| node_text(source, &tag).trim().to_string())
5789}
5790
5791fn source_line_end_col(source: &str, line: u32) -> u32 {
5792    source
5793        .lines()
5794        .nth(line as usize)
5795        .map(|line| line.len() as u32)
5796        .unwrap_or(0)
5797}
5798
5799fn extract_html_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
5800    let mut headings: Vec<(u8, Symbol)> = Vec::new();
5801    collect_html_headings(source, root, &mut headings);
5802
5803    let total_lines = source.lines().count() as u32;
5804
5805    // Extend each heading's end_line to just before the next heading at the
5806    // same or shallower level (or EOF). This makes aft_zoom return the full
5807    // section content rather than just the heading element's single line.
5808    for i in 0..headings.len() {
5809        let level = headings[i].0;
5810        let section_end = headings[i + 1..]
5811            .iter()
5812            .find(|(l, _)| *l <= level)
5813            .map(|(_, s)| s.range.start_line.saturating_sub(1))
5814            .unwrap_or_else(|| total_lines.saturating_sub(1));
5815        headings[i].1.range.end_line = section_end;
5816        if section_end != headings[i].1.range.start_line {
5817            headings[i].1.range.end_col = source_line_end_col(source, section_end);
5818        }
5819    }
5820
5821    // Build hierarchy: assign scope_chain and parent based on heading level
5822    let mut scope_stack: Vec<(u8, String)> = Vec::new(); // (level, name)
5823    for (level, symbol) in headings.iter_mut() {
5824        // Pop scope entries that are at the same level or deeper
5825        while scope_stack.last().is_some_and(|(l, _)| *l >= *level) {
5826            scope_stack.pop();
5827        }
5828        symbol.scope_chain = scope_stack.iter().map(|(_, name)| name.clone()).collect();
5829        symbol.parent = scope_stack.last().map(|(_, name)| name.clone());
5830        scope_stack.push((*level, symbol.name.clone()));
5831    }
5832
5833    Ok(headings.into_iter().map(|(_, s)| s).collect())
5834}
5835
5836/// Recursively collect h1-h6 elements from the HTML tree.
5837fn collect_html_headings(source: &str, node: &Node, headings: &mut Vec<(u8, Symbol)>) {
5838    let mut cursor = node.walk();
5839    if !cursor.goto_first_child() {
5840        return;
5841    }
5842
5843    loop {
5844        let child = cursor.node();
5845        if child.kind() == "element" {
5846            // Check if this element's start tag is h1-h6
5847            if let Some(start_tag) = child
5848                .child_by_field_name("start_tag")
5849                .or_else(|| child.child(0).filter(|c| c.kind() == "start_tag"))
5850            {
5851                if let Some(tag_name_node) = start_tag
5852                    .child_by_field_name("tag_name")
5853                    .or_else(|| start_tag.child(1).filter(|c| c.kind() == "tag_name"))
5854                {
5855                    let tag_name = node_text(source, &tag_name_node).to_lowercase();
5856                    if let Some(level) = match tag_name.as_str() {
5857                        "h1" => Some(1u8),
5858                        "h2" => Some(2),
5859                        "h3" => Some(3),
5860                        "h4" => Some(4),
5861                        "h5" => Some(5),
5862                        "h6" => Some(6),
5863                        _ => None,
5864                    } {
5865                        // Extract text content from the element
5866                        let text = extract_element_text(source, &child).trim().to_string();
5867                        if !text.is_empty() {
5868                            let range = node_range(&child);
5869                            let signature = format!("<h{}> {}", level, text);
5870                            headings.push((
5871                                level,
5872                                Symbol {
5873                                    name: text,
5874                                    kind: SymbolKind::Heading,
5875                                    range,
5876                                    signature: Some(signature),
5877                                    scope_chain: vec![], // filled later
5878                                    exported: false,
5879                                    parent: None, // filled later
5880                                },
5881                            ));
5882                        }
5883                    }
5884                }
5885            }
5886            // Recurse into element children (nested headings)
5887            collect_html_headings(source, &child, headings);
5888        } else {
5889            // Recurse into other node types (document, body, etc.)
5890            collect_html_headings(source, &child, headings);
5891        }
5892
5893        if !cursor.goto_next_sibling() {
5894            break;
5895        }
5896    }
5897}
5898
5899/// Extract text content from an HTML element, stripping tags.
5900fn extract_element_text(source: &str, node: &Node) -> String {
5901    let mut text = String::new();
5902    let mut cursor = node.walk();
5903    if !cursor.goto_first_child() {
5904        return text;
5905    }
5906    loop {
5907        let child = cursor.node();
5908        match child.kind() {
5909            "text" => {
5910                text.push_str(node_text(source, &child));
5911            }
5912            "element" => {
5913                // Recurse into nested elements (e.g., <strong>, <em>, <a>)
5914                text.push_str(&extract_element_text(source, &child));
5915            }
5916            _ => {}
5917        }
5918        if !cursor.goto_next_sibling() {
5919            break;
5920        }
5921    }
5922    text
5923}
5924
5925fn markdown_atx_heading_level(line: &str) -> Option<u8> {
5926    let trimmed = line.trim_start();
5927    let level = trimmed.bytes().take_while(|byte| *byte == b'#').count();
5928    if !(1..=6).contains(&level) {
5929        return None;
5930    }
5931
5932    let rest = &trimmed[level..];
5933    let marker_is_closed = match rest.chars().next() {
5934        Some(ch) => ch.is_whitespace(),
5935        None => true,
5936    };
5937    marker_is_closed.then_some(level as u8)
5938}
5939
5940/// Extract markdown headings as symbols.
5941/// Each heading becomes a symbol with kind `Heading`, and its range covers the entire
5942/// section (from the heading to the next heading at the same or higher level, or EOF).
5943fn extract_md_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
5944    let mut symbols = Vec::new();
5945    extract_md_sections(source, root, &mut symbols, &[]);
5946    Ok(symbols)
5947}
5948
5949/// Recursively walk `section` nodes to build the heading hierarchy.
5950fn extract_md_sections(
5951    source: &str,
5952    node: &Node,
5953    symbols: &mut Vec<Symbol>,
5954    scope_chain: &[String],
5955) {
5956    let mut cursor = node.walk();
5957    if !cursor.goto_first_child() {
5958        return;
5959    }
5960
5961    loop {
5962        let child = cursor.node();
5963        match child.kind() {
5964            "section" => {
5965                // A section contains an atx_heading as its first child,
5966                // followed by content and possibly nested sections.
5967                let mut section_cursor = child.walk();
5968                let mut heading_name = String::new();
5969                let mut heading_level: u8 = 0;
5970
5971                if section_cursor.goto_first_child() {
5972                    loop {
5973                        let section_child = section_cursor.node();
5974                        if section_child.kind() == "atx_heading" {
5975                            // Extract heading level from marker type
5976                            let mut h_cursor = section_child.walk();
5977                            if h_cursor.goto_first_child() {
5978                                loop {
5979                                    let h_child = h_cursor.node();
5980                                    let kind = h_child.kind();
5981                                    if kind.starts_with("atx_h") && kind.ends_with("_marker") {
5982                                        // "atx_h1_marker" → level 1, "atx_h2_marker" → level 2, etc.
5983                                        heading_level = kind
5984                                            .strip_prefix("atx_h")
5985                                            .and_then(|s| s.strip_suffix("_marker"))
5986                                            .and_then(|s| s.parse::<u8>().ok())
5987                                            .unwrap_or(1);
5988                                    } else if h_child.kind() == "inline" {
5989                                        heading_name =
5990                                            node_text(source, &h_child).trim().to_string();
5991                                    }
5992                                    if !h_cursor.goto_next_sibling() {
5993                                        break;
5994                                    }
5995                                }
5996                            }
5997                        }
5998                        if !section_cursor.goto_next_sibling() {
5999                            break;
6000                        }
6001                    }
6002                }
6003
6004                if !heading_name.is_empty() {
6005                    let mut range = node_range(&child);
6006                    if let Some(next_heading_level) = source
6007                        .lines()
6008                        .nth(range.end_line as usize)
6009                        .and_then(markdown_atx_heading_level)
6010                    {
6011                        if next_heading_level <= heading_level && range.end_line > range.start_line
6012                        {
6013                            range.end_line -= 1;
6014                            range.end_col = source_line_end_col(source, range.end_line);
6015                        }
6016                    }
6017                    let signature = format!(
6018                        "{} {}",
6019                        "#".repeat((heading_level as usize).min(6)),
6020                        heading_name
6021                    );
6022
6023                    symbols.push(Symbol {
6024                        name: heading_name.clone(),
6025                        kind: SymbolKind::Heading,
6026                        range,
6027                        signature: Some(signature),
6028                        scope_chain: scope_chain.to_vec(),
6029                        exported: false,
6030                        parent: scope_chain.last().cloned(),
6031                    });
6032
6033                    // Recurse into the section for nested headings
6034                    let mut new_scope = scope_chain.to_vec();
6035                    new_scope.push(heading_name);
6036                    extract_md_sections(source, &child, symbols, &new_scope);
6037                }
6038            }
6039            _ => {}
6040        }
6041
6042        if !cursor.goto_next_sibling() {
6043            break;
6044        }
6045    }
6046}
6047
6048/// Remove duplicate symbols based on (name, kind, start_line).
6049/// Class declarations can match both "class" and "method" patterns,
6050/// producing duplicates.
6051fn dedup_symbols(symbols: &mut Vec<Symbol>) {
6052    let mut seen = std::collections::HashSet::new();
6053    symbols.retain(|s| {
6054        let key = (s.name.clone(), format!("{:?}", s.kind), s.range.start_line);
6055        seen.insert(key)
6056    });
6057}
6058
6059/// Provider that uses tree-sitter for real symbol extraction.
6060/// Implements the `LanguageProvider` trait from `language.rs`.
6061pub struct TreeSitterProvider {
6062    parser: RefCell<FileParser>,
6063}
6064
6065#[derive(Debug, Clone)]
6066struct ReExportTarget {
6067    file: PathBuf,
6068    symbol_name: String,
6069}
6070
6071impl TreeSitterProvider {
6072    /// Create a new `TreeSitterProvider` backed by a fresh `FileParser`.
6073    pub fn new() -> Self {
6074        Self::with_symbol_cache(Arc::new(RwLock::new(SymbolCache::new())))
6075    }
6076
6077    /// Create a new `TreeSitterProvider` backed by a shared symbol cache.
6078    pub fn with_symbol_cache(symbol_cache: SharedSymbolCache) -> Self {
6079        Self {
6080            parser: RefCell::new(FileParser::with_symbol_cache(symbol_cache)),
6081        }
6082    }
6083
6084    /// Return shared symbol cache entries for status reporting.
6085    pub fn symbol_cache_len(&self) -> usize {
6086        let parser = self.parser.borrow();
6087        parser.symbol_cache_len()
6088    }
6089
6090    /// Shared symbol cache backing this provider.
6091    pub fn symbol_cache(&self) -> SharedSymbolCache {
6092        let parser = self.parser.borrow();
6093        parser.symbol_cache()
6094    }
6095
6096    fn resolve_symbol_inner(
6097        &self,
6098        file: &Path,
6099        name: &str,
6100        depth: usize,
6101        visited: &mut HashSet<(PathBuf, String)>,
6102    ) -> Result<Vec<SymbolMatch>, AftError> {
6103        if depth > MAX_REEXPORT_DEPTH {
6104            return Ok(Vec::new());
6105        }
6106
6107        let canonical_file = std::fs::canonicalize(file).unwrap_or_else(|_| file.to_path_buf());
6108        if !visited.insert((canonical_file, name.to_string())) {
6109            return Ok(Vec::new());
6110        }
6111
6112        let symbols = self.parser.borrow_mut().extract_symbols(file)?;
6113        let local_matches = symbol_matches_in_file(file, &symbols, name);
6114        if !local_matches.is_empty() {
6115            return Ok(local_matches);
6116        }
6117
6118        if name == "default" {
6119            let default_matches = self.resolve_local_default_export(file, &symbols)?;
6120            if !default_matches.is_empty() {
6121                return Ok(default_matches);
6122            }
6123        }
6124
6125        let reexport_targets = self.collect_reexport_targets(file, name)?;
6126        let mut matches = Vec::new();
6127        let mut seen = HashSet::new();
6128        for target in reexport_targets {
6129            for resolved in
6130                self.resolve_symbol_inner(&target.file, &target.symbol_name, depth + 1, visited)?
6131            {
6132                let key = format!(
6133                    "{}:{}:{}:{}:{}:{}",
6134                    resolved.file,
6135                    resolved.symbol.name,
6136                    resolved.symbol.range.start_line,
6137                    resolved.symbol.range.start_col,
6138                    resolved.symbol.range.end_line,
6139                    resolved.symbol.range.end_col
6140                );
6141                if seen.insert(key) {
6142                    matches.push(resolved);
6143                }
6144            }
6145        }
6146
6147        Ok(matches)
6148    }
6149
6150    fn collect_reexport_targets(
6151        &self,
6152        file: &Path,
6153        requested_name: &str,
6154    ) -> Result<Vec<ReExportTarget>, AftError> {
6155        let (source, tree, lang) = self.read_parsed_file(file)?;
6156        if !matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
6157            return Ok(Vec::new());
6158        }
6159
6160        let mut targets = Vec::new();
6161        let root = tree.root_node();
6162        let from_dir = file.parent().unwrap_or_else(|| Path::new("."));
6163
6164        let mut cursor = root.walk();
6165        if !cursor.goto_first_child() {
6166            return Ok(targets);
6167        }
6168
6169        loop {
6170            let node = cursor.node();
6171            if node.kind() == "export_statement" {
6172                let Some(source_node) = node.child_by_field_name("source") else {
6173                    if let Some(export_clause) = find_child_by_kind(node, "export_clause") {
6174                        if let Some(symbol_name) =
6175                            resolve_export_clause_name(&source, &export_clause, requested_name)
6176                        {
6177                            targets.push(ReExportTarget {
6178                                file: file.to_path_buf(),
6179                                symbol_name,
6180                            });
6181                        }
6182                    }
6183                    if !cursor.goto_next_sibling() {
6184                        break;
6185                    }
6186                    continue;
6187                };
6188
6189                let Some(module_path) = string_content(&source, &source_node) else {
6190                    if !cursor.goto_next_sibling() {
6191                        break;
6192                    }
6193                    continue;
6194                };
6195
6196                let Some(target_file) = resolve_module_path(from_dir, &module_path) else {
6197                    if !cursor.goto_next_sibling() {
6198                        break;
6199                    }
6200                    continue;
6201                };
6202
6203                if let Some(export_clause) = find_child_by_kind(node, "export_clause") {
6204                    if let Some(symbol_name) =
6205                        resolve_export_clause_name(&source, &export_clause, requested_name)
6206                    {
6207                        targets.push(ReExportTarget {
6208                            file: target_file,
6209                            symbol_name,
6210                        });
6211                    }
6212                } else if export_statement_has_wildcard(&source, &node) {
6213                    targets.push(ReExportTarget {
6214                        file: target_file,
6215                        symbol_name: requested_name.to_string(),
6216                    });
6217                }
6218            }
6219
6220            if !cursor.goto_next_sibling() {
6221                break;
6222            }
6223        }
6224
6225        Ok(targets)
6226    }
6227
6228    fn resolve_local_default_export(
6229        &self,
6230        file: &Path,
6231        symbols: &[Symbol],
6232    ) -> Result<Vec<SymbolMatch>, AftError> {
6233        let (source, tree, lang) = self.read_parsed_file(file)?;
6234        if !matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
6235            return Ok(Vec::new());
6236        }
6237
6238        let root = tree.root_node();
6239        let mut matches = Vec::new();
6240        let mut seen = HashSet::new();
6241
6242        let mut cursor = root.walk();
6243        if !cursor.goto_first_child() {
6244            return Ok(matches);
6245        }
6246
6247        loop {
6248            let node = cursor.node();
6249            if node.kind() == "export_statement"
6250                && node.child_by_field_name("source").is_none()
6251                && node_contains_token(&source, &node, "default")
6252            {
6253                if let Some(target_name) = default_export_target_name(&source, &node) {
6254                    for symbol_match in symbol_matches_in_file(file, symbols, &target_name) {
6255                        let key = format!(
6256                            "{}:{}:{}:{}:{}:{}",
6257                            symbol_match.file,
6258                            symbol_match.symbol.name,
6259                            symbol_match.symbol.range.start_line,
6260                            symbol_match.symbol.range.start_col,
6261                            symbol_match.symbol.range.end_line,
6262                            symbol_match.symbol.range.end_col
6263                        );
6264                        if seen.insert(key) {
6265                            matches.push(symbol_match);
6266                        }
6267                    }
6268                }
6269            }
6270
6271            if !cursor.goto_next_sibling() {
6272                break;
6273            }
6274        }
6275
6276        Ok(matches)
6277    }
6278
6279    fn read_parsed_file(&self, file: &Path) -> Result<(String, Tree, LangId), AftError> {
6280        let source = std::fs::read_to_string(file).map_err(|e| AftError::FileNotFound {
6281            path: format!("{}: {}", file.display(), e),
6282        })?;
6283        let (tree, lang) = {
6284            let mut parser = self.parser.borrow_mut();
6285            parser.parse_cloned(file)?
6286        };
6287        Ok((source, tree, lang))
6288    }
6289}
6290
6291fn symbol_matches_in_file(file: &Path, symbols: &[Symbol], name: &str) -> Vec<SymbolMatch> {
6292    symbols
6293        .iter()
6294        .filter(|symbol| symbol.name == name)
6295        .cloned()
6296        .map(|symbol| SymbolMatch {
6297            file: file.display().to_string(),
6298            symbol,
6299        })
6300        .collect()
6301}
6302
6303fn string_content(source: &str, node: &Node) -> Option<String> {
6304    let text = node_text(source, node);
6305    if text.len() < 2 {
6306        return None;
6307    }
6308
6309    Some(
6310        text.trim_start_matches(|c| c == '\'' || c == '"')
6311            .trim_end_matches(|c| c == '\'' || c == '"')
6312            .to_string(),
6313    )
6314}
6315
6316fn find_child_by_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
6317    let mut cursor = node.walk();
6318    if !cursor.goto_first_child() {
6319        return None;
6320    }
6321
6322    loop {
6323        let child = cursor.node();
6324        if child.kind() == kind {
6325            return Some(child);
6326        }
6327        if !cursor.goto_next_sibling() {
6328            break;
6329        }
6330    }
6331
6332    None
6333}
6334
6335fn resolve_export_clause_name(
6336    source: &str,
6337    export_clause: &Node,
6338    requested_name: &str,
6339) -> Option<String> {
6340    let mut cursor = export_clause.walk();
6341    if !cursor.goto_first_child() {
6342        return None;
6343    }
6344
6345    loop {
6346        let child = cursor.node();
6347        if child.kind() == "export_specifier" {
6348            let (source_name, exported_name) = export_specifier_names(source, &child)?;
6349            if exported_name == requested_name {
6350                return Some(source_name);
6351            }
6352        }
6353
6354        if !cursor.goto_next_sibling() {
6355            break;
6356        }
6357    }
6358
6359    None
6360}
6361
6362fn export_specifier_names(source: &str, specifier: &Node) -> Option<(String, String)> {
6363    let source_name = specifier
6364        .child_by_field_name("name")
6365        .map(|node| node_text(source, &node).to_string());
6366    let alias_name = specifier
6367        .child_by_field_name("alias")
6368        .map(|node| node_text(source, &node).to_string());
6369
6370    if let Some(source_name) = source_name {
6371        let exported_name = alias_name.unwrap_or_else(|| source_name.clone());
6372        return Some((source_name, exported_name));
6373    }
6374
6375    let mut names = Vec::new();
6376    let mut cursor = specifier.walk();
6377    if cursor.goto_first_child() {
6378        loop {
6379            let child = cursor.node();
6380            let child_text = node_text(source, &child).trim();
6381            if matches!(
6382                child.kind(),
6383                "identifier" | "type_identifier" | "property_identifier"
6384            ) || child_text == "default"
6385            {
6386                names.push(child_text.to_string());
6387            }
6388            if !cursor.goto_next_sibling() {
6389                break;
6390            }
6391        }
6392    }
6393
6394    match names.as_slice() {
6395        [name] => Some((name.clone(), name.clone())),
6396        [source_name, exported_name, ..] => Some((source_name.clone(), exported_name.clone())),
6397        _ => None,
6398    }
6399}
6400
6401fn export_statement_has_wildcard(source: &str, node: &Node) -> bool {
6402    let mut cursor = node.walk();
6403    if !cursor.goto_first_child() {
6404        return false;
6405    }
6406
6407    loop {
6408        if node_text(source, &cursor.node()).trim() == "*" {
6409            return true;
6410        }
6411        if !cursor.goto_next_sibling() {
6412            break;
6413        }
6414    }
6415
6416    false
6417}
6418
6419fn node_contains_token(source: &str, node: &Node, token: &str) -> bool {
6420    let mut cursor = node.walk();
6421    if !cursor.goto_first_child() {
6422        return false;
6423    }
6424
6425    loop {
6426        if node_text(source, &cursor.node()).trim() == token {
6427            return true;
6428        }
6429        if !cursor.goto_next_sibling() {
6430            break;
6431        }
6432    }
6433
6434    false
6435}
6436
6437fn default_export_target_name(source: &str, export_stmt: &Node) -> Option<String> {
6438    if let Some(value_node) = export_stmt.child_by_field_name("value") {
6439        if let Some(name) = default_export_node_name(source, &value_node) {
6440            return Some(name);
6441        }
6442    }
6443
6444    if let Some(declaration_node) = export_stmt.child_by_field_name("declaration") {
6445        if let Some(name) = default_export_node_name(source, &declaration_node) {
6446            return Some(name);
6447        }
6448    }
6449
6450    let mut cursor = export_stmt.walk();
6451    if !cursor.goto_first_child() {
6452        return None;
6453    }
6454
6455    loop {
6456        let child = cursor.node();
6457        if let Some(name) = default_export_node_name(source, &child) {
6458            return Some(name);
6459        }
6460
6461        if !cursor.goto_next_sibling() {
6462            break;
6463        }
6464    }
6465
6466    None
6467}
6468
6469fn default_export_node_name(source: &str, node: &Node) -> Option<String> {
6470    match node.kind() {
6471        "function_declaration"
6472        | "generator_function_declaration"
6473        | "function_expression"
6474        | "generator_function"
6475        | "class_declaration"
6476        | "class" => node
6477            .child_by_field_name("name")
6478            .map(|name_node| node_text(source, &name_node).to_string())
6479            .or_else(|| Some("default".to_string())),
6480        "interface_declaration" | "enum_declaration" | "type_alias_declaration" => node
6481            .child_by_field_name("name")
6482            .map(|name_node| node_text(source, &name_node).to_string()),
6483        "lexical_declaration" => lexical_declaration_name(source, node),
6484        "identifier" | "type_identifier" => {
6485            let text = node_text(source, node);
6486            (text != "export" && text != "default").then(|| text.to_string())
6487        }
6488        _ => None,
6489    }
6490}
6491
6492fn lexical_declaration_name(source: &str, node: &Node) -> Option<String> {
6493    let mut cursor = node.walk();
6494    if !cursor.goto_first_child() {
6495        return None;
6496    }
6497
6498    loop {
6499        let child = cursor.node();
6500        if child.kind() == "variable_declarator" {
6501            if let Some(name_node) = child.child_by_field_name("name") {
6502                return Some(node_text(source, &name_node).to_string());
6503            }
6504        }
6505        if !cursor.goto_next_sibling() {
6506            break;
6507        }
6508    }
6509
6510    None
6511}
6512
6513impl crate::language::LanguageProvider for TreeSitterProvider {
6514    fn resolve_symbol(&self, file: &Path, name: &str) -> Result<Vec<SymbolMatch>, AftError> {
6515        let matches = self.resolve_symbol_inner(file, name, 0, &mut HashSet::new())?;
6516
6517        if matches.is_empty() {
6518            Err(AftError::SymbolNotFound {
6519                name: name.to_string(),
6520                file: file.display().to_string(),
6521            })
6522        } else {
6523            Ok(matches)
6524        }
6525    }
6526
6527    fn list_symbols(&self, file: &Path) -> Result<Vec<Symbol>, AftError> {
6528        self.parser.borrow_mut().extract_symbols(file)
6529    }
6530
6531    fn as_any(&self) -> &dyn std::any::Any {
6532        self
6533    }
6534}
6535
6536#[cfg(test)]
6537mod tests {
6538    use super::*;
6539    use crate::language::LanguageProvider;
6540    use crate::symbol_cache_disk;
6541    use std::path::{Path, PathBuf};
6542
6543    fn fixture_path(name: &str) -> PathBuf {
6544        PathBuf::from(env!("CARGO_MANIFEST_DIR"))
6545            .join("tests")
6546            .join("fixtures")
6547            .join(name)
6548    }
6549
6550    fn test_symbol(name: &str) -> Symbol {
6551        Symbol {
6552            name: name.to_string(),
6553            kind: SymbolKind::Function,
6554            range: Range {
6555                start_line: 0,
6556                start_col: 0,
6557                end_line: 0,
6558                end_col: 10,
6559            },
6560            signature: Some(format!("fn {name}()")),
6561            scope_chain: Vec::new(),
6562            exported: true,
6563            parent: None,
6564        }
6565    }
6566
6567    #[test]
6568    fn inc_and_scss_extensions_are_detected() {
6569        assert_eq!(
6570            detect_language(Path::new("template.inc")),
6571            Some(LangId::Php)
6572        );
6573        assert_eq!(
6574            detect_language(Path::new("styles.scss")),
6575            Some(LangId::Scss)
6576        );
6577        assert_eq!(detect_language(Path::new("main.pas")), Some(LangId::Pascal));
6578        assert_eq!(detect_language(Path::new("main.pp")), Some(LangId::Pascal));
6579        assert_eq!(detect_language(Path::new("main.dpr")), Some(LangId::Pascal));
6580        assert_eq!(detect_language(Path::new("main.dpk")), Some(LangId::Pascal));
6581        assert_eq!(detect_language(Path::new("main.lpr")), Some(LangId::Pascal));
6582        assert_eq!(detect_language(Path::new("template.tpl")), None);
6583    }
6584
6585    #[test]
6586    fn inc_files_parse_with_php_grammar() {
6587        let tmp = tempfile::tempdir().expect("create temp dir");
6588        let file = tmp.path().join("partial.inc");
6589        std::fs::write(&file, "<?php\nfunction render_partial() { return 1; }\n")
6590            .expect("write inc file");
6591
6592        let mut parser = FileParser::new();
6593        let symbols = parser
6594            .extract_symbols(&file)
6595            .expect("extract php inc symbols");
6596        let function = symbols
6597            .iter()
6598            .find(|symbol| symbol.name == "render_partial")
6599            .expect("find PHP function in .inc");
6600        assert_eq!(function.kind, SymbolKind::Function);
6601    }
6602
6603    #[test]
6604    fn scss_symbols_include_mixin_variable_function_and_rule() {
6605        let tmp = tempfile::tempdir().expect("create temp dir");
6606        let file = tmp.path().join("styles.scss");
6607        std::fs::write(
6608            &file,
6609            r#"$brand-color: #336699;
6610
6611@mixin button-base($padding) {
6612  padding: $padding;
6613}
6614
6615@function double($value) {
6616  @return $value * 2;
6617}
6618
6619.card, .panel {
6620  color: $brand-color;
6621}
6622"#,
6623        )
6624        .expect("write scss file");
6625
6626        let mut parser = FileParser::new();
6627        let symbols = parser.extract_symbols(&file).expect("extract scss symbols");
6628        let get = |name: &str| {
6629            symbols
6630                .iter()
6631                .find(|symbol| symbol.name == name)
6632                .unwrap_or_else(|| panic!("missing {name}; got {symbols:?}"))
6633        };
6634
6635        assert_eq!(get("button-base").kind, SymbolKind::Function);
6636        assert_eq!(get("double").kind, SymbolKind::Function);
6637        assert_eq!(get("$brand-color").kind, SymbolKind::Variable);
6638        assert_eq!(get(".card, .panel").kind, SymbolKind::Class);
6639    }
6640
6641    #[test]
6642    fn symbol_cache_load_from_disk_round_trips_synthetic_entry() {
6643        let project = tempfile::tempdir().expect("create project dir");
6644        let storage = tempfile::tempdir().expect("create storage dir");
6645        let source = project.path().join("src/lib.rs");
6646        std::fs::create_dir_all(source.parent().expect("source parent"))
6647            .expect("create source dir");
6648        std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
6649        let mtime = std::fs::metadata(&source)
6650            .expect("stat source")
6651            .modified()
6652            .expect("source mtime");
6653        let content = std::fs::read(&source).expect("read source");
6654        let size = content.len() as u64;
6655        let hash = crate::cache_freshness::hash_bytes(&content);
6656
6657        let mut cache = SymbolCache::new();
6658        cache.set_project_root(project.path().to_path_buf());
6659        cache.insert(
6660            source.clone(),
6661            mtime,
6662            size,
6663            hash,
6664            vec![test_symbol("cached")],
6665        );
6666        symbol_cache_disk::write_to_disk(&cache, storage.path(), "unit-project")
6667            .expect("write symbol cache");
6668
6669        let mut restored = SymbolCache::new();
6670        let loaded = restored.load_from_disk(storage.path(), "unit-project", project.path());
6671        let symbols = restored.get(&source, mtime).expect("restored symbols");
6672
6673        assert_eq!(loaded, 1);
6674        assert_eq!(symbols.len(), 1);
6675        assert_eq!(symbols[0].name, "cached");
6676    }
6677
6678    #[test]
6679    fn symbol_cache_load_from_disk_drops_stale_synthetic_entry() {
6680        let project = tempfile::tempdir().expect("create project dir");
6681        let storage = tempfile::tempdir().expect("create storage dir");
6682        let source = project.path().join("src/lib.rs");
6683        std::fs::create_dir_all(source.parent().expect("source parent"))
6684            .expect("create source dir");
6685        std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
6686        let mtime = std::fs::metadata(&source)
6687            .expect("stat source")
6688            .modified()
6689            .expect("source mtime");
6690        let content = std::fs::read(&source).expect("read source");
6691        let size = content.len() as u64;
6692        let hash = crate::cache_freshness::hash_bytes(&content);
6693
6694        let mut cache = SymbolCache::new();
6695        cache.set_project_root(project.path().to_path_buf());
6696        cache.insert(
6697            source.clone(),
6698            mtime,
6699            size,
6700            hash,
6701            vec![test_symbol("cached")],
6702        );
6703        symbol_cache_disk::write_to_disk(&cache, storage.path(), "stale-unit-project")
6704            .expect("write symbol cache");
6705
6706        std::fs::write(&source, "pub fn cached() {}\npub fn fresh() {}\n").expect("change source");
6707
6708        let mut restored = SymbolCache::new();
6709        let loaded = restored.load_from_disk(storage.path(), "stale-unit-project", project.path());
6710
6711        assert_eq!(loaded, 0);
6712        assert_eq!(restored.len(), 0);
6713    }
6714
6715    #[test]
6716    fn stale_prewarm_generation_cannot_repopulate_symbol_cache_after_reset() {
6717        let project = tempfile::tempdir().expect("create project dir");
6718        let storage = tempfile::tempdir().expect("create storage dir");
6719        let source = project.path().join("src/lib.rs");
6720        std::fs::create_dir_all(source.parent().expect("source parent"))
6721            .expect("create source dir");
6722        std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
6723        let mtime = std::fs::metadata(&source)
6724            .expect("stat source")
6725            .modified()
6726            .expect("source mtime");
6727        let content = std::fs::read(&source).expect("read source");
6728        let size = content.len() as u64;
6729        let hash = crate::cache_freshness::hash_bytes(&content);
6730
6731        let mut disk_cache = SymbolCache::new();
6732        disk_cache.set_project_root(project.path().to_path_buf());
6733        disk_cache.insert(
6734            source.clone(),
6735            mtime,
6736            size,
6737            hash,
6738            vec![test_symbol("cached")],
6739        );
6740        symbol_cache_disk::write_to_disk(&disk_cache, storage.path(), "prewarm-reset")
6741            .expect("write symbol cache");
6742
6743        let shared = Arc::new(RwLock::new(SymbolCache::new()));
6744        let stale_generation = shared.write().unwrap().reset();
6745        let active_generation = shared.write().unwrap().reset();
6746        assert_ne!(stale_generation, active_generation);
6747
6748        {
6749            let mut cache = shared.write().unwrap();
6750            assert!(!cache
6751                .set_project_root_for_generation(stale_generation, project.path().to_path_buf()));
6752            assert_eq!(
6753                cache.load_from_disk_for_generation(
6754                    stale_generation,
6755                    storage.path(),
6756                    "prewarm-reset",
6757                    project.path()
6758                ),
6759                0
6760            );
6761        }
6762
6763        let mut stale_parser =
6764            FileParser::with_symbol_cache_generation(Arc::clone(&shared), Some(stale_generation));
6765        stale_parser
6766            .extract_symbols(&source)
6767            .expect("stale prewarm parses source but must not write cache");
6768
6769        let cache = shared.read().unwrap();
6770        assert_eq!(cache.generation(), active_generation);
6771        assert_eq!(cache.len(), 0);
6772        assert!(cache.project_root().is_none());
6773        assert!(!cache.contains_key(&source));
6774    }
6775
6776    // --- Language detection ---
6777
6778    #[test]
6779    fn detect_ts() {
6780        assert_eq!(
6781            detect_language(Path::new("foo.ts")),
6782            Some(LangId::TypeScript)
6783        );
6784    }
6785
6786    #[test]
6787    fn detect_tsx() {
6788        assert_eq!(detect_language(Path::new("foo.tsx")), Some(LangId::Tsx));
6789    }
6790
6791    #[test]
6792    fn detect_js() {
6793        assert_eq!(
6794            detect_language(Path::new("foo.js")),
6795            Some(LangId::JavaScript)
6796        );
6797    }
6798
6799    #[test]
6800    fn detect_jsx() {
6801        assert_eq!(
6802            detect_language(Path::new("foo.jsx")),
6803            Some(LangId::JavaScript)
6804        );
6805    }
6806
6807    #[test]
6808    fn detect_py() {
6809        assert_eq!(detect_language(Path::new("foo.py")), Some(LangId::Python));
6810    }
6811
6812    #[test]
6813    fn detect_rs() {
6814        assert_eq!(detect_language(Path::new("foo.rs")), Some(LangId::Rust));
6815    }
6816
6817    #[test]
6818    fn detect_go() {
6819        assert_eq!(detect_language(Path::new("foo.go")), Some(LangId::Go));
6820    }
6821
6822    #[test]
6823    fn detect_c() {
6824        assert_eq!(detect_language(Path::new("foo.c")), Some(LangId::C));
6825    }
6826
6827    #[test]
6828    fn detect_h() {
6829        assert_eq!(detect_language(Path::new("foo.h")), Some(LangId::C));
6830    }
6831
6832    #[test]
6833    fn detect_cc() {
6834        assert_eq!(detect_language(Path::new("foo.cc")), Some(LangId::Cpp));
6835    }
6836
6837    #[test]
6838    fn detect_cpp() {
6839        assert_eq!(detect_language(Path::new("foo.cpp")), Some(LangId::Cpp));
6840    }
6841
6842    #[test]
6843    fn detect_cxx() {
6844        assert_eq!(detect_language(Path::new("foo.cxx")), Some(LangId::Cpp));
6845    }
6846
6847    #[test]
6848    fn detect_hpp() {
6849        assert_eq!(detect_language(Path::new("foo.hpp")), Some(LangId::Cpp));
6850    }
6851
6852    #[test]
6853    fn detect_hh() {
6854        assert_eq!(detect_language(Path::new("foo.hh")), Some(LangId::Cpp));
6855    }
6856
6857    #[test]
6858    fn detect_zig() {
6859        assert_eq!(detect_language(Path::new("foo.zig")), Some(LangId::Zig));
6860    }
6861
6862    #[test]
6863    fn detect_cs() {
6864        assert_eq!(detect_language(Path::new("foo.cs")), Some(LangId::CSharp));
6865    }
6866
6867    #[test]
6868    fn detect_unknown_returns_none() {
6869        assert_eq!(detect_language(Path::new("foo.txt")), None);
6870    }
6871
6872    // --- Unsupported extension error ---
6873
6874    #[test]
6875    fn unsupported_extension_returns_invalid_request() {
6876        // Use a file that exists but has an unsupported extension
6877        let path = fixture_path("sample.ts");
6878        let bad_path = path.with_extension("txt");
6879        // Create a dummy file so the error comes from language detection, not I/O
6880        std::fs::write(&bad_path, "hello").unwrap();
6881        let provider = TreeSitterProvider::new();
6882        let result = provider.list_symbols(&bad_path);
6883        std::fs::remove_file(&bad_path).ok();
6884        match result {
6885            Err(AftError::InvalidRequest { message }) => {
6886                assert!(
6887                    message.contains("unsupported file extension"),
6888                    "msg: {}",
6889                    message
6890                );
6891                assert!(message.contains("txt"), "msg: {}", message);
6892            }
6893            other => panic!("expected InvalidRequest, got {:?}", other),
6894        }
6895    }
6896
6897    // --- TypeScript extraction ---
6898
6899    #[test]
6900    fn ts_extracts_all_symbol_kinds() {
6901        let provider = TreeSitterProvider::new();
6902        let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
6903
6904        let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
6905        assert!(
6906            names.contains(&"greet"),
6907            "missing function greet: {:?}",
6908            names
6909        );
6910        assert!(names.contains(&"add"), "missing arrow fn add: {:?}", names);
6911        assert!(
6912            names.contains(&"UserService"),
6913            "missing class UserService: {:?}",
6914            names
6915        );
6916        assert!(
6917            names.contains(&"Config"),
6918            "missing interface Config: {:?}",
6919            names
6920        );
6921        assert!(
6922            names.contains(&"Status"),
6923            "missing enum Status: {:?}",
6924            names
6925        );
6926        assert!(
6927            names.contains(&"UserId"),
6928            "missing type alias UserId: {:?}",
6929            names
6930        );
6931        assert!(
6932            names.contains(&"internalHelper"),
6933            "missing non-exported fn: {:?}",
6934            names
6935        );
6936
6937        // At least 6 unique symbols as required
6938        assert!(
6939            symbols.len() >= 6,
6940            "expected ≥6 symbols, got {}: {:?}",
6941            symbols.len(),
6942            names
6943        );
6944    }
6945
6946    #[test]
6947    fn ts_symbol_kinds_correct() {
6948        let provider = TreeSitterProvider::new();
6949        let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
6950
6951        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
6952
6953        assert_eq!(find("greet").kind, SymbolKind::Function);
6954        assert_eq!(find("add").kind, SymbolKind::Function); // arrow fn → Function
6955        assert_eq!(find("UserService").kind, SymbolKind::Class);
6956        assert_eq!(find("Config").kind, SymbolKind::Interface);
6957        assert_eq!(find("Status").kind, SymbolKind::Enum);
6958        assert_eq!(find("UserId").kind, SymbolKind::TypeAlias);
6959    }
6960
6961    #[test]
6962    fn ts_export_detection() {
6963        let provider = TreeSitterProvider::new();
6964        let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
6965
6966        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
6967
6968        assert!(find("greet").exported, "greet should be exported");
6969        assert!(find("add").exported, "add should be exported");
6970        assert!(
6971            find("UserService").exported,
6972            "UserService should be exported"
6973        );
6974        assert!(find("Config").exported, "Config should be exported");
6975        assert!(find("Status").exported, "Status should be exported");
6976        assert!(
6977            !find("internalHelper").exported,
6978            "internalHelper should not be exported"
6979        );
6980    }
6981
6982    #[test]
6983    fn ts_method_scope_chain() {
6984        let provider = TreeSitterProvider::new();
6985        let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
6986
6987        let methods: Vec<&Symbol> = symbols
6988            .iter()
6989            .filter(|s| s.kind == SymbolKind::Method)
6990            .collect();
6991        assert!(!methods.is_empty(), "should have at least one method");
6992
6993        for method in &methods {
6994            assert_eq!(
6995                method.scope_chain,
6996                vec!["UserService"],
6997                "method {} should have UserService in scope chain",
6998                method.name
6999            );
7000            assert_eq!(method.parent.as_deref(), Some("UserService"));
7001        }
7002    }
7003
7004    #[test]
7005    fn ts_signatures_present() {
7006        let provider = TreeSitterProvider::new();
7007        let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
7008
7009        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7010
7011        let greet_sig = find("greet").signature.as_ref().unwrap();
7012        assert!(
7013            greet_sig.contains("greet"),
7014            "signature should contain function name: {}",
7015            greet_sig
7016        );
7017    }
7018
7019    #[test]
7020    fn ts_ranges_valid() {
7021        let provider = TreeSitterProvider::new();
7022        let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
7023
7024        for s in &symbols {
7025            assert!(
7026                s.range.end_line >= s.range.start_line,
7027                "symbol {} has invalid range: {:?}",
7028                s.name,
7029                s.range
7030            );
7031        }
7032    }
7033
7034    // --- JavaScript extraction ---
7035
7036    #[test]
7037    fn js_extracts_core_symbols() {
7038        let provider = TreeSitterProvider::new();
7039        let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
7040
7041        let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
7042        assert!(
7043            names.contains(&"multiply"),
7044            "missing function multiply: {:?}",
7045            names
7046        );
7047        assert!(
7048            names.contains(&"divide"),
7049            "missing arrow fn divide: {:?}",
7050            names
7051        );
7052        assert!(
7053            names.contains(&"EventEmitter"),
7054            "missing class EventEmitter: {:?}",
7055            names
7056        );
7057        assert!(
7058            names.contains(&"main"),
7059            "missing default export fn main: {:?}",
7060            names
7061        );
7062
7063        assert!(
7064            symbols.len() >= 4,
7065            "expected ≥4 symbols, got {}: {:?}",
7066            symbols.len(),
7067            names
7068        );
7069    }
7070
7071    #[test]
7072    fn js_arrow_fn_correctly_named() {
7073        let provider = TreeSitterProvider::new();
7074        let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
7075
7076        let divide = symbols.iter().find(|s| s.name == "divide").unwrap();
7077        assert_eq!(divide.kind, SymbolKind::Function);
7078        assert!(divide.exported, "divide should be exported");
7079
7080        let internal = symbols.iter().find(|s| s.name == "internalUtil").unwrap();
7081        assert_eq!(internal.kind, SymbolKind::Function);
7082        assert!(!internal.exported, "internalUtil should not be exported");
7083    }
7084
7085    #[test]
7086    fn js_method_scope_chain() {
7087        let provider = TreeSitterProvider::new();
7088        let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
7089
7090        let methods: Vec<&Symbol> = symbols
7091            .iter()
7092            .filter(|s| s.kind == SymbolKind::Method)
7093            .collect();
7094
7095        for method in &methods {
7096            assert_eq!(
7097                method.scope_chain,
7098                vec!["EventEmitter"],
7099                "method {} should have EventEmitter in scope chain",
7100                method.name
7101            );
7102        }
7103    }
7104
7105    // --- TSX extraction ---
7106
7107    #[test]
7108    fn tsx_extracts_react_component() {
7109        let provider = TreeSitterProvider::new();
7110        let symbols = provider.list_symbols(&fixture_path("sample.tsx")).unwrap();
7111
7112        let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
7113        assert!(
7114            names.contains(&"Button"),
7115            "missing React component Button: {:?}",
7116            names
7117        );
7118        assert!(
7119            names.contains(&"Counter"),
7120            "missing class Counter: {:?}",
7121            names
7122        );
7123        assert!(
7124            names.contains(&"formatLabel"),
7125            "missing function formatLabel: {:?}",
7126            names
7127        );
7128
7129        assert!(
7130            symbols.len() >= 2,
7131            "expected ≥2 symbols, got {}: {:?}",
7132            symbols.len(),
7133            names
7134        );
7135    }
7136
7137    #[test]
7138    fn tsx_jsx_doesnt_break_parser() {
7139        // Main assertion: TSX grammar handles JSX without errors
7140        let provider = TreeSitterProvider::new();
7141        let result = provider.list_symbols(&fixture_path("sample.tsx"));
7142        assert!(
7143            result.is_ok(),
7144            "TSX parsing should succeed: {:?}",
7145            result.err()
7146        );
7147    }
7148
7149    // --- resolve_symbol ---
7150
7151    #[test]
7152    fn resolve_symbol_finds_match() {
7153        let provider = TreeSitterProvider::new();
7154        let matches = provider
7155            .resolve_symbol(&fixture_path("sample.ts"), "greet")
7156            .unwrap();
7157        assert_eq!(matches.len(), 1);
7158        assert_eq!(matches[0].symbol.name, "greet");
7159        assert_eq!(matches[0].symbol.kind, SymbolKind::Function);
7160    }
7161
7162    #[test]
7163    fn resolve_symbol_not_found() {
7164        let provider = TreeSitterProvider::new();
7165        let result = provider.resolve_symbol(&fixture_path("sample.ts"), "nonexistent");
7166        assert!(matches!(result, Err(AftError::SymbolNotFound { .. })));
7167    }
7168
7169    #[test]
7170    fn resolve_symbol_follows_reexport_chains() {
7171        let dir = tempfile::tempdir().unwrap();
7172        let config = dir.path().join("config.ts");
7173        let barrel1 = dir.path().join("barrel1.ts");
7174        let barrel2 = dir.path().join("barrel2.ts");
7175        let barrel3 = dir.path().join("barrel3.ts");
7176        let index = dir.path().join("index.ts");
7177
7178        std::fs::write(
7179            &config,
7180            "export class Config {}\nexport default class DefaultConfig {}\n",
7181        )
7182        .unwrap();
7183        std::fs::write(
7184            &barrel1,
7185            "export { Config } from './config';\nexport { default as NamedDefault } from './config';\n",
7186        )
7187        .unwrap();
7188        std::fs::write(
7189            &barrel2,
7190            "export { Config as RenamedConfig } from './barrel1';\n",
7191        )
7192        .unwrap();
7193        std::fs::write(
7194            &barrel3,
7195            "export * from './barrel2';\nexport * from './barrel1';\n",
7196        )
7197        .unwrap();
7198        std::fs::write(
7199            &index,
7200            "export class Config {}\nexport { RenamedConfig as FinalConfig } from './barrel3';\nexport * from './barrel3';\n",
7201        )
7202        .unwrap();
7203
7204        let provider = TreeSitterProvider::new();
7205        let config_canon = std::fs::canonicalize(&config).unwrap();
7206
7207        let direct = provider.resolve_symbol(&barrel1, "Config").unwrap();
7208        assert_eq!(direct.len(), 1);
7209        assert_eq!(direct[0].symbol.name, "Config");
7210        assert_eq!(direct[0].file, config_canon.display().to_string());
7211
7212        let renamed = provider.resolve_symbol(&barrel2, "RenamedConfig").unwrap();
7213        assert_eq!(renamed.len(), 1);
7214        assert_eq!(renamed[0].symbol.name, "Config");
7215        assert_eq!(renamed[0].file, config_canon.display().to_string());
7216
7217        let wildcard_chain = provider.resolve_symbol(&index, "FinalConfig").unwrap();
7218        assert_eq!(wildcard_chain.len(), 1);
7219        assert_eq!(wildcard_chain[0].symbol.name, "Config");
7220        assert_eq!(wildcard_chain[0].file, config_canon.display().to_string());
7221
7222        let wildcard_default = provider.resolve_symbol(&index, "NamedDefault").unwrap();
7223        assert_eq!(wildcard_default.len(), 1);
7224        assert_eq!(wildcard_default[0].symbol.name, "DefaultConfig");
7225        assert_eq!(wildcard_default[0].file, config_canon.display().to_string());
7226
7227        let local = provider.resolve_symbol(&index, "Config").unwrap();
7228        assert_eq!(local.len(), 1);
7229        assert_eq!(local[0].symbol.name, "Config");
7230        assert_eq!(local[0].file, index.display().to_string());
7231    }
7232
7233    // --- Parse tree caching ---
7234
7235    #[test]
7236    fn symbol_range_includes_rust_attributes() {
7237        let dir = tempfile::tempdir().unwrap();
7238        let path = dir.path().join("test_attrs.rs");
7239        std::fs::write(
7240            &path,
7241            "/// This is a doc comment\n#[test]\n#[cfg(test)]\nfn my_test_fn() {\n    assert!(true);\n}\n",
7242        )
7243        .unwrap();
7244
7245        let provider = TreeSitterProvider::new();
7246        let matches = provider.resolve_symbol(&path, "my_test_fn").unwrap();
7247        assert_eq!(matches.len(), 1);
7248        assert_eq!(
7249            matches[0].symbol.range.start_line, 0,
7250            "symbol range should include preceding /// doc comment, got start_line={}",
7251            matches[0].symbol.range.start_line
7252        );
7253    }
7254
7255    #[test]
7256    fn symbol_range_includes_go_doc_comment() {
7257        let dir = tempfile::tempdir().unwrap();
7258        let path = dir.path().join("test_doc.go");
7259        std::fs::write(
7260            &path,
7261            "package main\n\n// MyFunc does something useful.\n// It has a multi-line doc.\nfunc MyFunc() {\n}\n",
7262        )
7263        .unwrap();
7264
7265        let provider = TreeSitterProvider::new();
7266        let matches = provider.resolve_symbol(&path, "MyFunc").unwrap();
7267        assert_eq!(matches.len(), 1);
7268        assert_eq!(
7269            matches[0].symbol.range.start_line, 2,
7270            "symbol range should include preceding doc comments, got start_line={}",
7271            matches[0].symbol.range.start_line
7272        );
7273    }
7274
7275    #[test]
7276    fn symbol_range_skips_unrelated_comments() {
7277        let dir = tempfile::tempdir().unwrap();
7278        let path = dir.path().join("test_gap.go");
7279        std::fs::write(
7280            &path,
7281            "package main\n\n// This is a standalone comment\n\nfunc Standalone() {\n}\n",
7282        )
7283        .unwrap();
7284
7285        let provider = TreeSitterProvider::new();
7286        let matches = provider.resolve_symbol(&path, "Standalone").unwrap();
7287        assert_eq!(matches.len(), 1);
7288        assert_eq!(
7289            matches[0].symbol.range.start_line, 4,
7290            "symbol range should NOT include comment separated by blank line, got start_line={}",
7291            matches[0].symbol.range.start_line
7292        );
7293    }
7294
7295    #[test]
7296    fn parse_cache_returns_same_tree() {
7297        let mut parser = FileParser::new();
7298        let path = fixture_path("sample.ts");
7299
7300        let (tree1, _) = parser.parse(&path).unwrap();
7301        let tree1_root = tree1.root_node().byte_range();
7302
7303        let (tree2, _) = parser.parse(&path).unwrap();
7304        let tree2_root = tree2.root_node().byte_range();
7305
7306        // Same tree (cache hit) should return identical root node range
7307        assert_eq!(tree1_root, tree2_root);
7308    }
7309
7310    #[test]
7311    fn extract_symbols_from_tree_matches_list_symbols() {
7312        let path = fixture_path("sample.rs");
7313        let source = std::fs::read_to_string(&path).unwrap();
7314
7315        let provider = TreeSitterProvider::new();
7316        let listed = provider.list_symbols(&path).unwrap();
7317
7318        let mut parser = FileParser::new();
7319        let (tree, lang) = parser.parse(&path).unwrap();
7320        let extracted = extract_symbols_from_tree(&source, tree, lang).unwrap();
7321
7322        assert_eq!(symbols_as_debug(&extracted), symbols_as_debug(&listed));
7323    }
7324
7325    fn symbols_as_debug(symbols: &[Symbol]) -> Vec<String> {
7326        symbols
7327            .iter()
7328            .map(|symbol| {
7329                format!(
7330                    "{}|{:?}|{}:{}-{}:{}|{:?}|{:?}|{}|{:?}",
7331                    symbol.name,
7332                    symbol.kind,
7333                    symbol.range.start_line,
7334                    symbol.range.start_col,
7335                    symbol.range.end_line,
7336                    symbol.range.end_col,
7337                    symbol.signature,
7338                    symbol.scope_chain,
7339                    symbol.exported,
7340                    symbol.parent,
7341                )
7342            })
7343            .collect()
7344    }
7345
7346    // --- Python extraction ---
7347
7348    #[test]
7349    fn py_extracts_all_symbols() {
7350        let provider = TreeSitterProvider::new();
7351        let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
7352
7353        let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
7354        assert!(
7355            names.contains(&"top_level_function"),
7356            "missing top_level_function: {:?}",
7357            names
7358        );
7359        assert!(names.contains(&"MyClass"), "missing MyClass: {:?}", names);
7360        assert!(
7361            names.contains(&"instance_method"),
7362            "missing method instance_method: {:?}",
7363            names
7364        );
7365        assert!(
7366            names.contains(&"decorated_function"),
7367            "missing decorated_function: {:?}",
7368            names
7369        );
7370
7371        // Plan requires ≥4 symbols
7372        assert!(
7373            symbols.len() >= 4,
7374            "expected ≥4 symbols, got {}: {:?}",
7375            symbols.len(),
7376            names
7377        );
7378    }
7379
7380    #[test]
7381    fn py_symbol_kinds_correct() {
7382        let provider = TreeSitterProvider::new();
7383        let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
7384
7385        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7386
7387        assert_eq!(find("top_level_function").kind, SymbolKind::Function);
7388        assert_eq!(find("MyClass").kind, SymbolKind::Class);
7389        assert_eq!(find("instance_method").kind, SymbolKind::Method);
7390        assert_eq!(find("decorated_function").kind, SymbolKind::Function);
7391        assert_eq!(find("OuterClass").kind, SymbolKind::Class);
7392        assert_eq!(find("InnerClass").kind, SymbolKind::Class);
7393        assert_eq!(find("inner_method").kind, SymbolKind::Method);
7394        assert_eq!(find("outer_method").kind, SymbolKind::Method);
7395    }
7396
7397    #[test]
7398    fn py_method_scope_chain() {
7399        let provider = TreeSitterProvider::new();
7400        let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
7401
7402        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7403
7404        // Method inside MyClass
7405        assert_eq!(
7406            find("instance_method").scope_chain,
7407            vec!["MyClass"],
7408            "instance_method should have MyClass in scope chain"
7409        );
7410        assert_eq!(find("instance_method").parent.as_deref(), Some("MyClass"));
7411
7412        // Method inside OuterClass > InnerClass
7413        assert_eq!(
7414            find("inner_method").scope_chain,
7415            vec!["OuterClass", "InnerClass"],
7416            "inner_method should have nested scope chain"
7417        );
7418
7419        // InnerClass itself should have OuterClass in scope
7420        assert_eq!(
7421            find("InnerClass").scope_chain,
7422            vec!["OuterClass"],
7423            "InnerClass should have OuterClass in scope"
7424        );
7425
7426        // Top-level function has empty scope
7427        assert!(
7428            find("top_level_function").scope_chain.is_empty(),
7429            "top-level function should have empty scope chain"
7430        );
7431    }
7432
7433    #[test]
7434    fn py_decorated_function_signature() {
7435        let provider = TreeSitterProvider::new();
7436        let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
7437
7438        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7439
7440        let sig = find("decorated_function").signature.as_ref().unwrap();
7441        assert!(
7442            sig.contains("@staticmethod"),
7443            "decorated function signature should include decorator: {}",
7444            sig
7445        );
7446        assert!(
7447            sig.contains("def decorated_function"),
7448            "signature should include function def: {}",
7449            sig
7450        );
7451    }
7452
7453    #[test]
7454    fn py_ranges_valid() {
7455        let provider = TreeSitterProvider::new();
7456        let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
7457
7458        for s in &symbols {
7459            assert!(
7460                s.range.end_line >= s.range.start_line,
7461                "symbol {} has invalid range: {:?}",
7462                s.name,
7463                s.range
7464            );
7465        }
7466    }
7467
7468    // --- Rust extraction ---
7469
7470    #[test]
7471    fn rs_extracts_all_symbols() {
7472        let provider = TreeSitterProvider::new();
7473        let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
7474
7475        let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
7476        assert!(
7477            names.contains(&"public_function"),
7478            "missing public_function: {:?}",
7479            names
7480        );
7481        assert!(
7482            names.contains(&"private_function"),
7483            "missing private_function: {:?}",
7484            names
7485        );
7486        assert!(names.contains(&"MyStruct"), "missing MyStruct: {:?}", names);
7487        assert!(names.contains(&"Color"), "missing enum Color: {:?}", names);
7488        assert!(
7489            names.contains(&"Drawable"),
7490            "missing trait Drawable: {:?}",
7491            names
7492        );
7493        // impl methods
7494        assert!(
7495            names.contains(&"new"),
7496            "missing impl method new: {:?}",
7497            names
7498        );
7499
7500        // Plan requires ≥6 symbols
7501        assert!(
7502            symbols.len() >= 6,
7503            "expected ≥6 symbols, got {}: {:?}",
7504            symbols.len(),
7505            names
7506        );
7507    }
7508
7509    #[test]
7510    fn rs_symbol_kinds_correct() {
7511        let provider = TreeSitterProvider::new();
7512        let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
7513
7514        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7515
7516        assert_eq!(find("public_function").kind, SymbolKind::Function);
7517        assert_eq!(find("private_function").kind, SymbolKind::Function);
7518        assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
7519        assert_eq!(find("Color").kind, SymbolKind::Enum);
7520        assert_eq!(find("Drawable").kind, SymbolKind::Interface); // trait → Interface
7521        assert_eq!(find("new").kind, SymbolKind::Method);
7522    }
7523
7524    #[test]
7525    fn rs_pub_export_detection() {
7526        let provider = TreeSitterProvider::new();
7527        let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
7528
7529        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7530
7531        assert!(
7532            find("public_function").exported,
7533            "pub fn should be exported"
7534        );
7535        assert!(
7536            !find("private_function").exported,
7537            "non-pub fn should not be exported"
7538        );
7539        assert!(find("MyStruct").exported, "pub struct should be exported");
7540        assert!(find("Color").exported, "pub enum should be exported");
7541        assert!(find("Drawable").exported, "pub trait should be exported");
7542        assert!(
7543            find("new").exported,
7544            "pub fn inside impl should be exported"
7545        );
7546        assert!(
7547            !find("helper").exported,
7548            "non-pub fn inside impl should not be exported"
7549        );
7550    }
7551
7552    #[test]
7553    fn rs_impl_method_scope_chain() {
7554        let provider = TreeSitterProvider::new();
7555        let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
7556
7557        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7558
7559        // `impl MyStruct { fn new() }` → scope chain = ["MyStruct"]
7560        assert_eq!(
7561            find("new").scope_chain,
7562            vec!["MyStruct"],
7563            "impl method should have type in scope chain"
7564        );
7565        assert_eq!(find("new").parent.as_deref(), Some("MyStruct"));
7566
7567        // Free function has empty scope chain
7568        assert!(
7569            find("public_function").scope_chain.is_empty(),
7570            "free function should have empty scope chain"
7571        );
7572    }
7573
7574    #[test]
7575    fn rs_trait_impl_scope_chain() {
7576        let provider = TreeSitterProvider::new();
7577        let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
7578
7579        // `impl Drawable for MyStruct { fn draw() }` → scope = ["Drawable for MyStruct"]
7580        let draw = symbols.iter().find(|s| s.name == "draw").unwrap();
7581        assert_eq!(
7582            draw.scope_chain,
7583            vec!["Drawable for MyStruct"],
7584            "trait impl method should have 'Trait for Type' scope"
7585        );
7586        assert_eq!(draw.parent.as_deref(), Some("MyStruct"));
7587    }
7588
7589    #[test]
7590    fn rs_ranges_valid() {
7591        let provider = TreeSitterProvider::new();
7592        let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
7593
7594        for s in &symbols {
7595            assert!(
7596                s.range.end_line >= s.range.start_line,
7597                "symbol {} has invalid range: {:?}",
7598                s.name,
7599                s.range
7600            );
7601        }
7602    }
7603
7604    // --- Go extraction ---
7605
7606    #[test]
7607    fn go_extracts_all_symbols() {
7608        let provider = TreeSitterProvider::new();
7609        let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
7610
7611        let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
7612        assert!(
7613            names.contains(&"ExportedFunction"),
7614            "missing ExportedFunction: {:?}",
7615            names
7616        );
7617        assert!(
7618            names.contains(&"unexportedFunction"),
7619            "missing unexportedFunction: {:?}",
7620            names
7621        );
7622        assert!(
7623            names.contains(&"MyStruct"),
7624            "missing struct MyStruct: {:?}",
7625            names
7626        );
7627        assert!(
7628            names.contains(&"Reader"),
7629            "missing interface Reader: {:?}",
7630            names
7631        );
7632        // receiver method
7633        assert!(
7634            names.contains(&"String"),
7635            "missing receiver method String: {:?}",
7636            names
7637        );
7638
7639        // Plan requires ≥4 symbols
7640        assert!(
7641            symbols.len() >= 4,
7642            "expected ≥4 symbols, got {}: {:?}",
7643            symbols.len(),
7644            names
7645        );
7646    }
7647
7648    #[test]
7649    fn go_symbol_kinds_correct() {
7650        let provider = TreeSitterProvider::new();
7651        let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
7652
7653        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7654
7655        assert_eq!(find("ExportedFunction").kind, SymbolKind::Function);
7656        assert_eq!(find("unexportedFunction").kind, SymbolKind::Function);
7657        assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
7658        assert_eq!(find("Reader").kind, SymbolKind::Interface);
7659        assert_eq!(find("String").kind, SymbolKind::Method);
7660        assert_eq!(find("helper").kind, SymbolKind::Method);
7661    }
7662
7663    #[test]
7664    fn go_uppercase_export_detection() {
7665        let provider = TreeSitterProvider::new();
7666        let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
7667
7668        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7669
7670        assert!(
7671            find("ExportedFunction").exported,
7672            "ExportedFunction (uppercase) should be exported"
7673        );
7674        assert!(
7675            !find("unexportedFunction").exported,
7676            "unexportedFunction (lowercase) should not be exported"
7677        );
7678        assert!(
7679            find("MyStruct").exported,
7680            "MyStruct (uppercase) should be exported"
7681        );
7682        assert!(
7683            find("Reader").exported,
7684            "Reader (uppercase) should be exported"
7685        );
7686        assert!(
7687            find("String").exported,
7688            "String method (uppercase) should be exported"
7689        );
7690        assert!(
7691            !find("helper").exported,
7692            "helper method (lowercase) should not be exported"
7693        );
7694    }
7695
7696    #[test]
7697    fn go_receiver_method_scope_chain() {
7698        let provider = TreeSitterProvider::new();
7699        let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
7700
7701        let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7702
7703        // `func (m *MyStruct) String()` → scope chain = ["MyStruct"]
7704        assert_eq!(
7705            find("String").scope_chain,
7706            vec!["MyStruct"],
7707            "receiver method should have type in scope chain"
7708        );
7709        assert_eq!(find("String").parent.as_deref(), Some("MyStruct"));
7710
7711        // Regular function has empty scope chain
7712        assert!(
7713            find("ExportedFunction").scope_chain.is_empty(),
7714            "regular function should have empty scope chain"
7715        );
7716    }
7717
7718    #[test]
7719    fn go_ranges_valid() {
7720        let provider = TreeSitterProvider::new();
7721        let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
7722
7723        for s in &symbols {
7724            assert!(
7725                s.range.end_line >= s.range.start_line,
7726                "symbol {} has invalid range: {:?}",
7727                s.name,
7728                s.range
7729            );
7730        }
7731    }
7732
7733    // --- Cross-language ---
7734
7735    #[test]
7736    fn cross_language_all_six_produce_symbols() {
7737        let provider = TreeSitterProvider::new();
7738
7739        let fixtures = [
7740            ("sample.ts", "TypeScript"),
7741            ("sample.tsx", "TSX"),
7742            ("sample.js", "JavaScript"),
7743            ("sample.py", "Python"),
7744            ("sample.rs", "Rust"),
7745            ("sample.go", "Go"),
7746        ];
7747
7748        for (fixture, lang) in &fixtures {
7749            let symbols = provider
7750                .list_symbols(&fixture_path(fixture))
7751                .unwrap_or_else(|e| panic!("{} ({}) failed: {:?}", lang, fixture, e));
7752            assert!(
7753                symbols.len() >= 2,
7754                "{} should produce ≥2 symbols, got {}: {:?}",
7755                lang,
7756                symbols.len(),
7757                symbols.iter().map(|s| &s.name).collect::<Vec<_>>()
7758            );
7759        }
7760    }
7761
7762    // --- Symbol cache tests ---
7763
7764    #[test]
7765    fn symbol_cache_returns_cached_results_on_second_call() {
7766        let dir = tempfile::tempdir().unwrap();
7767        let file = dir.path().join("test.rs");
7768        std::fs::write(&file, "pub fn hello() {}\npub fn world() {}").unwrap();
7769
7770        let mut parser = FileParser::new();
7771
7772        let symbols1 = parser.extract_symbols(&file).unwrap();
7773        assert_eq!(symbols1.len(), 2);
7774
7775        // Second call should return cached result
7776        let symbols2 = parser.extract_symbols(&file).unwrap();
7777        assert_eq!(symbols2.len(), 2);
7778        assert_eq!(symbols1[0].name, symbols2[0].name);
7779
7780        // Verify cache is populated
7781        assert!(parser.symbol_cache.read().unwrap().contains_key(&file));
7782    }
7783
7784    #[test]
7785    fn symbol_cache_invalidates_on_file_change() {
7786        let dir = tempfile::tempdir().unwrap();
7787        let file = dir.path().join("test.rs");
7788        std::fs::write(&file, "pub fn hello() {}").unwrap();
7789
7790        let mut parser = FileParser::new();
7791
7792        let symbols1 = parser.extract_symbols(&file).unwrap();
7793        assert_eq!(symbols1.len(), 1);
7794        assert_eq!(symbols1[0].name, "hello");
7795
7796        // Wait to ensure mtime changes (filesystem resolution can be 1s on some OS)
7797        std::thread::sleep(std::time::Duration::from_millis(50));
7798
7799        // Modify file — add a second function
7800        std::fs::write(&file, "pub fn hello() {}\npub fn goodbye() {}").unwrap();
7801
7802        // Should detect mtime change and re-extract
7803        let symbols2 = parser.extract_symbols(&file).unwrap();
7804        assert_eq!(symbols2.len(), 2);
7805        assert!(symbols2.iter().any(|s| s.name == "goodbye"));
7806    }
7807
7808    #[test]
7809    fn symbol_cache_invalidate_method_clears_entry() {
7810        let dir = tempfile::tempdir().unwrap();
7811        let file = dir.path().join("test.rs");
7812        std::fs::write(&file, "pub fn hello() {}").unwrap();
7813
7814        let mut parser = FileParser::new();
7815        parser.extract_symbols(&file).unwrap();
7816        assert!(parser.symbol_cache.read().unwrap().contains_key(&file));
7817
7818        parser.invalidate_symbols(&file);
7819        assert!(!parser.symbol_cache.read().unwrap().contains_key(&file));
7820        // Parse tree cache should also be cleared
7821        assert!(!parser.cache.contains_key(&file));
7822    }
7823
7824    #[test]
7825    fn symbol_cache_works_for_multiple_languages() {
7826        let dir = tempfile::tempdir().unwrap();
7827        let rs_file = dir.path().join("lib.rs");
7828        let ts_file = dir.path().join("app.ts");
7829        let py_file = dir.path().join("main.py");
7830
7831        std::fs::write(&rs_file, "pub fn rust_fn() {}").unwrap();
7832        std::fs::write(&ts_file, "export function tsFn() {}").unwrap();
7833        std::fs::write(&py_file, "def py_fn():\n    pass").unwrap();
7834
7835        let mut parser = FileParser::new();
7836
7837        let rs_syms = parser.extract_symbols(&rs_file).unwrap();
7838        let ts_syms = parser.extract_symbols(&ts_file).unwrap();
7839        let py_syms = parser.extract_symbols(&py_file).unwrap();
7840
7841        assert!(rs_syms.iter().any(|s| s.name == "rust_fn"));
7842        assert!(ts_syms.iter().any(|s| s.name == "tsFn"));
7843        assert!(py_syms.iter().any(|s| s.name == "py_fn"));
7844
7845        // All should be cached now
7846        assert_eq!(parser.symbol_cache.read().unwrap().len(), 3);
7847
7848        // Re-extract should return same results from cache
7849        let rs_syms2 = parser.extract_symbols(&rs_file).unwrap();
7850        assert_eq!(rs_syms.len(), rs_syms2.len());
7851    }
7852
7853    #[test]
7854    fn extract_json_symbols_top_level_keys() {
7855        let dir = tempfile::tempdir().unwrap();
7856        let file = dir.path().join("package.json");
7857        std::fs::write(&file, r#"{"name": "x", "version": "1"}"#).unwrap();
7858
7859        let mut parser = FileParser::new();
7860        let symbols = parser.extract_symbols(&file).unwrap();
7861
7862        assert_eq!(symbols.len(), 2);
7863        assert!(symbols
7864            .iter()
7865            .any(|s| s.name == "name" && s.kind == SymbolKind::Variable));
7866        assert!(symbols
7867            .iter()
7868            .any(|s| s.name == "version" && s.kind == SymbolKind::Variable));
7869    }
7870
7871    #[test]
7872    fn extract_json_symbols_root_array() {
7873        let dir = tempfile::tempdir().unwrap();
7874        let file = dir.path().join("array.json");
7875        std::fs::write(&file, "[1,2,3]").unwrap();
7876
7877        let mut parser = FileParser::new();
7878        let symbols = parser.extract_symbols(&file).unwrap();
7879
7880        assert_eq!(symbols.len(), 0);
7881    }
7882
7883    #[test]
7884    fn extract_json_symbols_no_recursion_into_nested() {
7885        let dir = tempfile::tempdir().unwrap();
7886        let file = dir.path().join("nested.json");
7887        std::fs::write(&file, r#"{"scripts": {"build": "tsc"}}"#).unwrap();
7888
7889        let mut parser = FileParser::new();
7890        let symbols = parser.extract_symbols(&file).unwrap();
7891
7892        assert_eq!(symbols.len(), 1);
7893        assert_eq!(symbols[0].name, "scripts");
7894        assert!(!symbols.iter().any(|s| s.name == "build"));
7895    }
7896
7897    #[test]
7898    fn extract_scala_symbols_object_and_method() {
7899        let dir = tempfile::tempdir().unwrap();
7900        let file = dir.path().join("Greeter.scala");
7901        std::fs::write(
7902            &file,
7903            "object Greeter {\n  def hello(name: String): String = s\"hi $name\"\n}",
7904        )
7905        .unwrap();
7906
7907        let mut parser = FileParser::new();
7908        let symbols = parser.extract_symbols(&file).unwrap();
7909
7910        assert!(symbols
7911            .iter()
7912            .any(|s| s.name == "Greeter" && s.kind == SymbolKind::Class));
7913        assert!(symbols.iter().any(|s| s.name == "hello"
7914            && s.kind == SymbolKind::Method
7915            && s.scope_chain == vec!["Greeter".to_string()]));
7916    }
7917
7918    #[test]
7919    fn extract_scala_symbols_class_and_trait() {
7920        let dir = tempfile::tempdir().unwrap();
7921        let file = dir.path().join("Types.scala");
7922        std::fs::write(&file, "class Foo\ntrait Bar").unwrap();
7923
7924        let mut parser = FileParser::new();
7925        let symbols = parser.extract_symbols(&file).unwrap();
7926
7927        assert!(symbols
7928            .iter()
7929            .any(|s| s.name == "Foo" && s.kind == SymbolKind::Class));
7930        assert!(symbols
7931            .iter()
7932            .any(|s| s.name == "Bar" && s.kind == SymbolKind::Interface));
7933    }
7934
7935    #[test]
7936    fn extract_yaml_symbols_k8s_resource() {
7937        let dir = tempfile::tempdir().unwrap();
7938        let file = dir.path().join("deployment.yaml");
7939        std::fs::write(
7940            &file,
7941            r#"apiVersion: apps/v1
7942kind: Deployment
7943metadata:
7944  name: nginx
7945  namespace: web
7946"#,
7947        )
7948        .unwrap();
7949
7950        let mut parser = FileParser::new();
7951        let symbols = parser.extract_symbols(&file).unwrap();
7952
7953        assert_eq!(symbols.len(), 1, "Expected 1 symbol for K8s Deployment");
7954        let sym = &symbols[0];
7955        assert_eq!(sym.name, "web/Deployment/nginx");
7956        assert_eq!(sym.kind, SymbolKind::Class);
7957        assert!(sym.exported);
7958    }
7959
7960    #[test]
7961    fn extract_yaml_symbols_k8s_no_namespace() {
7962        let dir = tempfile::tempdir().unwrap();
7963        let file = dir.path().join("service.yaml");
7964        std::fs::write(
7965            &file,
7966            r#"apiVersion: v1
7967kind: Service
7968metadata:
7969  name: nginx-svc
7970"#,
7971        )
7972        .unwrap();
7973
7974        let mut parser = FileParser::new();
7975        let symbols = parser.extract_symbols(&file).unwrap();
7976
7977        assert_eq!(symbols.len(), 1, "Expected 1 symbol for K8s Service");
7978        let sym = &symbols[0];
7979        assert_eq!(sym.name, "Service/nginx-svc");
7980        assert_eq!(sym.kind, SymbolKind::Class);
7981    }
7982
7983    #[test]
7984    fn extract_yaml_symbols_multidoc() {
7985        let dir = tempfile::tempdir().unwrap();
7986        let file = dir.path().join("multidoc.yaml");
7987        std::fs::write(
7988            &file,
7989            r#"apiVersion: apps/v1
7990kind: Deployment
7991metadata:
7992  name: a
7993---
7994apiVersion: v1
7995kind: Service
7996metadata:
7997  name: b
7998"#,
7999        )
8000        .unwrap();
8001
8002        let mut parser = FileParser::new();
8003        let symbols = parser.extract_symbols(&file).unwrap();
8004
8005        assert_eq!(symbols.len(), 2, "Expected 2 symbols for multi-doc YAML");
8006        assert!(symbols.iter().any(|s| s.name == "Deployment/a"));
8007        assert!(symbols.iter().any(|s| s.name == "Service/b"));
8008    }
8009
8010    #[test]
8011    fn extract_yaml_symbols_generic_fallback() {
8012        let dir = tempfile::tempdir().unwrap();
8013        let file = dir.path().join("compose.yaml");
8014        std::fs::write(
8015            &file,
8016            r#"version: "3"
8017services:
8018  web: {}
8019volumes:
8020  data: {}
8021"#,
8022        )
8023        .unwrap();
8024
8025        let mut parser = FileParser::new();
8026        let symbols = parser.extract_symbols(&file).unwrap();
8027
8028        // Should have top-level keys: version, services, volumes
8029        assert_eq!(symbols.len(), 3, "Expected 3 symbols for generic YAML");
8030        assert!(symbols
8031            .iter()
8032            .any(|s| s.name == "version" && s.kind == SymbolKind::Variable));
8033        assert!(symbols
8034            .iter()
8035            .any(|s| s.name == "services" && s.kind == SymbolKind::Variable));
8036        assert!(symbols
8037            .iter()
8038            .any(|s| s.name == "volumes" && s.kind == SymbolKind::Variable));
8039    }
8040
8041    #[test]
8042    fn extract_yaml_symbols_empty() {
8043        let dir = tempfile::tempdir().unwrap();
8044        let file = dir.path().join("empty.yaml");
8045        std::fs::write(&file, "").unwrap();
8046
8047        let mut parser = FileParser::new();
8048        let symbols = parser.extract_symbols(&file).unwrap();
8049
8050        assert_eq!(symbols.len(), 0, "Expected 0 symbols for empty YAML");
8051    }
8052
8053    #[test]
8054    fn extract_yaml_symbols_resource_limits() {
8055        let dir = tempfile::tempdir().unwrap();
8056        let file = dir.path().join("deployment.yaml");
8057        std::fs::write(
8058            &file,
8059            r#"apiVersion: apps/v1
8060kind: Deployment
8061metadata:
8062  name: app
8063spec:
8064  template:
8065    spec:
8066      containers:
8067      - name: main
8068        image: myapp:1.0
8069        resources:
8070          limits:
8071            cpu: "2"
8072            memory: 1Gi
8073          requests:
8074            cpu: "1"
8075            memory: 512Mi
8076"#,
8077        )
8078        .unwrap();
8079
8080        let mut parser = FileParser::new();
8081        let symbols = parser.extract_symbols(&file).unwrap();
8082
8083        assert_eq!(symbols.len(), 1, "Expected 1 symbol for Deployment");
8084        let sym = &symbols[0];
8085        assert_eq!(sym.name, "Deployment/app");
8086        let sig = sym.signature.as_ref().unwrap();
8087        assert!(sig.contains("cpu="), "Signature should contain cpu= field");
8088        assert!(
8089            sig.contains("memory="),
8090            "Signature should contain memory= field"
8091        );
8092    }
8093
8094    #[test]
8095    fn extract_yaml_symbols_env_names() {
8096        let dir = tempfile::tempdir().unwrap();
8097        let file = dir.path().join("deployment.yaml");
8098        std::fs::write(
8099            &file,
8100            r#"apiVersion: apps/v1
8101kind: Deployment
8102metadata:
8103  name: app
8104spec:
8105  template:
8106    spec:
8107      containers:
8108      - name: main
8109        image: myapp:1.0
8110        env:
8111        - name: FOO
8112          value: "bar"
8113        - name: BAR
8114          value: "baz"
8115"#,
8116        )
8117        .unwrap();
8118
8119        let mut parser = FileParser::new();
8120        let symbols = parser.extract_symbols(&file).unwrap();
8121
8122        assert_eq!(symbols.len(), 1, "Expected 1 symbol for Deployment");
8123        let sym = &symbols[0];
8124        let sig = sym.signature.as_ref().unwrap();
8125        assert!(
8126            sig.contains("env=FOO,BAR"),
8127            "Signature should contain env=FOO,BAR"
8128        );
8129    }
8130
8131    #[test]
8132    fn extract_yaml_symbols_rbac_rules() {
8133        let dir = tempfile::tempdir().unwrap();
8134        let file = dir.path().join("role.yaml");
8135        std::fs::write(
8136            &file,
8137            r#"apiVersion: rbac.authorization.k8s.io/v1
8138kind: Role
8139metadata:
8140  name: reader
8141rules:
8142- apiGroups: [""]
8143  resources: [pods, services]
8144  verbs: [get, list, watch]
8145"#,
8146        )
8147        .unwrap();
8148
8149        let mut parser = FileParser::new();
8150        let symbols = parser.extract_symbols(&file).unwrap();
8151
8152        assert_eq!(symbols.len(), 1, "Expected 1 symbol for Role");
8153        let sym = &symbols[0];
8154        let sig = sym.signature.as_ref().unwrap();
8155        assert!(
8156            sig.contains("verbs=get,list,watch"),
8157            "Signature should contain verbs=get,list,watch"
8158        );
8159        assert!(
8160            sig.contains("resources=pods,services"),
8161            "Signature should contain resources=pods,services"
8162        );
8163    }
8164
8165    #[test]
8166    fn extract_yaml_symbols_argo_workflow() {
8167        let dir = tempfile::tempdir().unwrap();
8168        let file = dir.path().join("workflow.yaml");
8169        std::fs::write(
8170            &file,
8171            r#"apiVersion: argoproj.io/v1alpha1
8172kind: Workflow
8173metadata:
8174  name: hello-world
8175spec:
8176  entrypoint: main
8177  templates:
8178  - name: main
8179    container:
8180      image: alpine:3.18
8181      command: [echo]
8182      args: ["hello"]
8183  - name: print
8184    container:
8185      image: alpine:3.18
8186      command: [echo]
8187      args: ["world"]
8188"#,
8189        )
8190        .unwrap();
8191
8192        let mut parser = FileParser::new();
8193        let symbols = parser.extract_symbols(&file).unwrap();
8194
8195        assert_eq!(symbols.len(), 1, "Expected 1 symbol for Workflow");
8196        let sym = &symbols[0];
8197        assert!(
8198            sym.name.contains("Workflow"),
8199            "Symbol name should contain Workflow"
8200        );
8201        let sig = sym.signature.as_ref().unwrap();
8202        assert!(
8203            sig.contains("entrypoint=main"),
8204            "Signature should contain entrypoint=main"
8205        );
8206        assert!(
8207            sig.contains("templates=main,print"),
8208            "Signature should contain templates=main,print"
8209        );
8210        assert!(
8211            sig.contains("image=alpine:3.18"),
8212            "Signature should contain image=alpine:3.18"
8213        );
8214        assert!(
8215            sig.contains("command=echo"),
8216            "Signature should contain command=echo"
8217        );
8218    }
8219
8220    #[test]
8221    fn extract_yaml_symbols_generatename_fallback() {
8222        let dir = tempfile::tempdir().unwrap();
8223        let file = dir.path().join("workflow.yaml");
8224        std::fs::write(
8225            &file,
8226            r#"apiVersion: argoproj.io/v1alpha1
8227kind: Workflow
8228metadata:
8229  generateName: hello-
8230spec:
8231  entrypoint: main
8232  templates:
8233  - name: main
8234    container:
8235      image: alpine:3.18
8236"#,
8237        )
8238        .unwrap();
8239
8240        let mut parser = FileParser::new();
8241        let symbols = parser.extract_symbols(&file).unwrap();
8242
8243        assert_eq!(symbols.len(), 1, "Expected 1 symbol for Workflow");
8244        let sym = &symbols[0];
8245        assert!(
8246            sym.name.contains("hello-"),
8247            "Symbol name should contain generateName fallback 'hello-'"
8248        );
8249    }
8250
8251    #[test]
8252    fn extract_pascal_symbols_test() {
8253        let dir = tempfile::tempdir().unwrap();
8254        let file = dir.path().join("MyUnit.pas");
8255        std::fs::write(
8256            &file,
8257            r#"
8258unit MyUnit;
8259
8260interface
8261
8262uses SysUtils;
8263
8264type
8265  TMyClass = class
8266  private
8267    FValue: Integer;
8268  public
8269    constructor Create;
8270    procedure DoSomething; virtual;
8271  end;
8272
8273  TMyRecord = record
8274    X, Y: Integer;
8275  end;
8276
8277  IMyInterface = interface
8278    procedure DoSomethingElse;
8279  end;
8280
8281  TMyEnum = (Red, Green, Blue);
8282
8283const
8284  UNIT_CONST = 42;
8285
8286var
8287  UnitVar: string;
8288
8289implementation
8290
8291constructor TMyClass.Create;
8292begin
8293  FValue := 0;
8294end;
8295
8296procedure TMyClass.DoSomething;
8297begin
8298end;
8299
8300procedure StandaloneProc;
8301begin
8302end;
8303
8304end.
8305"#,
8306        )
8307        .unwrap();
8308
8309        let mut parser = FileParser::new();
8310        let symbols = parser.extract_symbols(&file).unwrap();
8311
8312        let get = |name: &str| {
8313            symbols
8314                .iter()
8315                .find(|symbol| symbol.name == name)
8316                .unwrap_or_else(|| panic!("missing {name}; got {symbols:?}"))
8317        };
8318
8319        assert_eq!(get("MyUnit").kind, SymbolKind::Class);
8320        assert_eq!(get("TMyClass").kind, SymbolKind::Class);
8321        assert_eq!(get("TMyRecord").kind, SymbolKind::Struct);
8322        assert_eq!(get("IMyInterface").kind, SymbolKind::Interface);
8323        assert_eq!(get("TMyEnum").kind, SymbolKind::Enum);
8324        assert_eq!(get("UNIT_CONST").kind, SymbolKind::Variable);
8325        assert_eq!(get("UnitVar").kind, SymbolKind::Variable);
8326        assert_eq!(get("StandaloneProc").kind, SymbolKind::Function);
8327
8328        // Methods inside TMyClass
8329        let create_methods: Vec<&Symbol> = symbols.iter().filter(|s| s.name == "Create").collect();
8330        assert_eq!(create_methods.len(), 2); // one in interface, one in implementation
8331        for m in create_methods {
8332            assert_eq!(m.kind, SymbolKind::Method);
8333            assert_eq!(m.scope_chain, vec!["TMyClass".to_string()]);
8334        }
8335
8336        let do_something_methods: Vec<&Symbol> =
8337            symbols.iter().filter(|s| s.name == "DoSomething").collect();
8338        assert_eq!(do_something_methods.len(), 2); // one in interface, one in implementation
8339        for m in do_something_methods {
8340            assert_eq!(m.kind, SymbolKind::Method);
8341            assert_eq!(m.scope_chain, vec!["TMyClass".to_string()]);
8342        }
8343    }
8344}