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