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