Skip to main content

aft/
parser.rs

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