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.name == name)
6581 .cloned()
6582 .map(|symbol| SymbolMatch {
6583 file: file.display().to_string(),
6584 symbol,
6585 })
6586 .collect()
6587}
6588
6589fn string_content(source: &str, node: &Node) -> Option<String> {
6590 let text = node_text(source, node);
6591 if text.len() < 2 {
6592 return None;
6593 }
6594
6595 Some(
6596 text.trim_start_matches(|c| c == '\'' || c == '"')
6597 .trim_end_matches(|c| c == '\'' || c == '"')
6598 .to_string(),
6599 )
6600}
6601
6602fn find_child_by_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
6603 let mut cursor = node.walk();
6604 if !cursor.goto_first_child() {
6605 return None;
6606 }
6607
6608 loop {
6609 let child = cursor.node();
6610 if child.kind() == kind {
6611 return Some(child);
6612 }
6613 if !cursor.goto_next_sibling() {
6614 break;
6615 }
6616 }
6617
6618 None
6619}
6620
6621fn resolve_export_clause_name(
6622 source: &str,
6623 export_clause: &Node,
6624 requested_name: &str,
6625) -> Option<String> {
6626 let mut cursor = export_clause.walk();
6627 if !cursor.goto_first_child() {
6628 return None;
6629 }
6630
6631 loop {
6632 let child = cursor.node();
6633 if child.kind() == "export_specifier" {
6634 let (source_name, exported_name) = export_specifier_names(source, &child)?;
6635 if exported_name == requested_name {
6636 return Some(source_name);
6637 }
6638 }
6639
6640 if !cursor.goto_next_sibling() {
6641 break;
6642 }
6643 }
6644
6645 None
6646}
6647
6648fn export_specifier_names(source: &str, specifier: &Node) -> Option<(String, String)> {
6649 let source_name = specifier
6650 .child_by_field_name("name")
6651 .map(|node| node_text(source, &node).to_string());
6652 let alias_name = specifier
6653 .child_by_field_name("alias")
6654 .map(|node| node_text(source, &node).to_string());
6655
6656 if let Some(source_name) = source_name {
6657 let exported_name = alias_name.unwrap_or_else(|| source_name.clone());
6658 return Some((source_name, exported_name));
6659 }
6660
6661 let mut names = Vec::new();
6662 let mut cursor = specifier.walk();
6663 if cursor.goto_first_child() {
6664 loop {
6665 let child = cursor.node();
6666 let child_text = node_text(source, &child).trim();
6667 if matches!(
6668 child.kind(),
6669 "identifier" | "type_identifier" | "property_identifier"
6670 ) || child_text == "default"
6671 {
6672 names.push(child_text.to_string());
6673 }
6674 if !cursor.goto_next_sibling() {
6675 break;
6676 }
6677 }
6678 }
6679
6680 match names.as_slice() {
6681 [name] => Some((name.clone(), name.clone())),
6682 [source_name, exported_name, ..] => Some((source_name.clone(), exported_name.clone())),
6683 _ => None,
6684 }
6685}
6686
6687fn export_statement_has_wildcard(source: &str, node: &Node) -> bool {
6688 let mut cursor = node.walk();
6689 if !cursor.goto_first_child() {
6690 return false;
6691 }
6692
6693 loop {
6694 if node_text(source, &cursor.node()).trim() == "*" {
6695 return true;
6696 }
6697 if !cursor.goto_next_sibling() {
6698 break;
6699 }
6700 }
6701
6702 false
6703}
6704
6705fn node_contains_token(source: &str, node: &Node, token: &str) -> bool {
6706 let mut cursor = node.walk();
6707 if !cursor.goto_first_child() {
6708 return false;
6709 }
6710
6711 loop {
6712 if node_text(source, &cursor.node()).trim() == token {
6713 return true;
6714 }
6715 if !cursor.goto_next_sibling() {
6716 break;
6717 }
6718 }
6719
6720 false
6721}
6722
6723fn default_export_target_name(source: &str, export_stmt: &Node) -> Option<String> {
6724 if let Some(value_node) = export_stmt.child_by_field_name("value") {
6725 if let Some(name) = default_export_node_name(source, &value_node) {
6726 return Some(name);
6727 }
6728 }
6729
6730 if let Some(declaration_node) = export_stmt.child_by_field_name("declaration") {
6731 if let Some(name) = default_export_node_name(source, &declaration_node) {
6732 return Some(name);
6733 }
6734 }
6735
6736 let mut cursor = export_stmt.walk();
6737 if !cursor.goto_first_child() {
6738 return None;
6739 }
6740
6741 loop {
6742 let child = cursor.node();
6743 if let Some(name) = default_export_node_name(source, &child) {
6744 return Some(name);
6745 }
6746
6747 if !cursor.goto_next_sibling() {
6748 break;
6749 }
6750 }
6751
6752 None
6753}
6754
6755fn default_export_node_name(source: &str, node: &Node) -> Option<String> {
6756 match node.kind() {
6757 "function_declaration"
6758 | "generator_function_declaration"
6759 | "function_expression"
6760 | "generator_function"
6761 | "class_declaration"
6762 | "class" => node
6763 .child_by_field_name("name")
6764 .map(|name_node| node_text(source, &name_node).to_string())
6765 .or_else(|| Some("default".to_string())),
6766 "interface_declaration" | "enum_declaration" | "type_alias_declaration" => node
6767 .child_by_field_name("name")
6768 .map(|name_node| node_text(source, &name_node).to_string()),
6769 "lexical_declaration" => lexical_declaration_name(source, node),
6770 "identifier" | "type_identifier" => {
6771 let text = node_text(source, node);
6772 (text != "export" && text != "default").then(|| text.to_string())
6773 }
6774 _ => None,
6775 }
6776}
6777
6778fn lexical_declaration_name(source: &str, node: &Node) -> Option<String> {
6779 let mut cursor = node.walk();
6780 if !cursor.goto_first_child() {
6781 return None;
6782 }
6783
6784 loop {
6785 let child = cursor.node();
6786 if child.kind() == "variable_declarator" {
6787 if let Some(name_node) = child.child_by_field_name("name") {
6788 return Some(node_text(source, &name_node).to_string());
6789 }
6790 }
6791 if !cursor.goto_next_sibling() {
6792 break;
6793 }
6794 }
6795
6796 None
6797}
6798
6799impl crate::language::LanguageProvider for TreeSitterProvider {
6800 fn resolve_symbol(&self, file: &Path, name: &str) -> Result<Vec<SymbolMatch>, AftError> {
6801 let mut parser = FileParser::with_symbol_cache(self.symbol_cache());
6802 let matches = self.resolve_symbol_inner(&mut parser, file, name, 0, &mut HashSet::new())?;
6803
6804 if matches.is_empty() {
6805 Err(AftError::SymbolNotFound {
6806 name: name.to_string(),
6807 file: file.display().to_string(),
6808 })
6809 } else {
6810 Ok(matches)
6811 }
6812 }
6813
6814 fn list_symbols(&self, file: &Path) -> Result<Vec<Symbol>, AftError> {
6815 let mut parser = FileParser::with_symbol_cache(self.symbol_cache());
6816 parser.extract_symbols(file)
6817 }
6818
6819 fn as_any(&self) -> &dyn std::any::Any {
6820 self
6821 }
6822}
6823
6824#[cfg(test)]
6825mod tests {
6826 use super::*;
6827 use crate::language::LanguageProvider;
6828 use crate::symbol_cache_disk;
6829 use std::path::{Path, PathBuf};
6830
6831 fn fixture_path(name: &str) -> PathBuf {
6832 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
6833 .join("tests")
6834 .join("fixtures")
6835 .join(name)
6836 }
6837
6838 fn test_symbol(name: &str) -> Symbol {
6839 Symbol {
6840 name: name.to_string(),
6841 kind: SymbolKind::Function,
6842 range: Range {
6843 start_line: 0,
6844 start_col: 0,
6845 end_line: 0,
6846 end_col: 10,
6847 },
6848 signature: Some(format!("fn {name}()")),
6849 scope_chain: Vec::new(),
6850 exported: true,
6851 parent: None,
6852 }
6853 }
6854
6855 #[test]
6856 fn inc_and_scss_extensions_are_detected() {
6857 assert_eq!(
6858 detect_language(Path::new("template.inc")),
6859 Some(LangId::Php)
6860 );
6861 assert_eq!(
6862 detect_language(Path::new("styles.scss")),
6863 Some(LangId::Scss)
6864 );
6865 assert_eq!(detect_language(Path::new("main.pas")), Some(LangId::Pascal));
6866 assert_eq!(detect_language(Path::new("main.pp")), Some(LangId::Pascal));
6867 assert_eq!(detect_language(Path::new("main.dpr")), Some(LangId::Pascal));
6868 assert_eq!(detect_language(Path::new("main.dpk")), Some(LangId::Pascal));
6869 assert_eq!(detect_language(Path::new("main.lpr")), Some(LangId::Pascal));
6870 assert_eq!(detect_language(Path::new("template.tpl")), None);
6871 }
6872
6873 #[test]
6874 fn inc_files_parse_with_php_grammar() {
6875 let tmp = tempfile::tempdir().expect("create temp dir");
6876 let file = tmp.path().join("partial.inc");
6877 std::fs::write(&file, "<?php\nfunction render_partial() { return 1; }\n")
6878 .expect("write inc file");
6879
6880 let mut parser = FileParser::new();
6881 let symbols = parser
6882 .extract_symbols(&file)
6883 .expect("extract php inc symbols");
6884 let function = symbols
6885 .iter()
6886 .find(|symbol| symbol.name == "render_partial")
6887 .expect("find PHP function in .inc");
6888 assert_eq!(function.kind, SymbolKind::Function);
6889 }
6890
6891 #[test]
6892 fn scss_symbols_include_mixin_variable_function_and_rule() {
6893 let tmp = tempfile::tempdir().expect("create temp dir");
6894 let file = tmp.path().join("styles.scss");
6895 std::fs::write(
6896 &file,
6897 r#"$brand-color: #336699;
6898
6899@mixin button-base($padding) {
6900 padding: $padding;
6901}
6902
6903@function double($value) {
6904 @return $value * 2;
6905}
6906
6907.card, .panel {
6908 color: $brand-color;
6909}
6910"#,
6911 )
6912 .expect("write scss file");
6913
6914 let mut parser = FileParser::new();
6915 let symbols = parser.extract_symbols(&file).expect("extract scss symbols");
6916 let get = |name: &str| {
6917 symbols
6918 .iter()
6919 .find(|symbol| symbol.name == name)
6920 .unwrap_or_else(|| panic!("missing {name}; got {symbols:?}"))
6921 };
6922
6923 assert_eq!(get("button-base").kind, SymbolKind::Function);
6924 assert_eq!(get("double").kind, SymbolKind::Function);
6925 assert_eq!(get("$brand-color").kind, SymbolKind::Variable);
6926 assert_eq!(get(".card, .panel").kind, SymbolKind::Class);
6927 }
6928
6929 #[test]
6930 fn symbol_cache_load_from_disk_round_trips_synthetic_entry() {
6931 let project = tempfile::tempdir().expect("create project dir");
6932 let storage = tempfile::tempdir().expect("create storage dir");
6933 let source = project.path().join("src/lib.rs");
6934 std::fs::create_dir_all(source.parent().expect("source parent"))
6935 .expect("create source dir");
6936 std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
6937 let mtime = std::fs::metadata(&source)
6938 .expect("stat source")
6939 .modified()
6940 .expect("source mtime");
6941 let content = std::fs::read(&source).expect("read source");
6942 let size = content.len() as u64;
6943 let hash = crate::cache_freshness::hash_bytes(&content);
6944
6945 let mut cache = SymbolCache::new();
6946 cache.set_project_root(project.path().to_path_buf());
6947 cache.insert(
6948 source.clone(),
6949 mtime,
6950 size,
6951 hash,
6952 vec![test_symbol("cached")],
6953 );
6954 symbol_cache_disk::write_to_disk(&cache, storage.path(), "unit-project")
6955 .expect("write symbol cache");
6956
6957 let mut restored = SymbolCache::new();
6958 let loaded = restored.load_from_disk(storage.path(), "unit-project", project.path());
6959 let symbols = restored.get(&source, mtime).expect("restored symbols");
6960
6961 assert_eq!(loaded, 1);
6962 assert_eq!(symbols.len(), 1);
6963 assert_eq!(symbols[0].name, "cached");
6964 }
6965
6966 #[test]
6967 fn symbol_cache_load_from_disk_drops_stale_synthetic_entry() {
6968 let project = tempfile::tempdir().expect("create project dir");
6969 let storage = tempfile::tempdir().expect("create storage dir");
6970 let source = project.path().join("src/lib.rs");
6971 std::fs::create_dir_all(source.parent().expect("source parent"))
6972 .expect("create source dir");
6973 std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
6974 let mtime = std::fs::metadata(&source)
6975 .expect("stat source")
6976 .modified()
6977 .expect("source mtime");
6978 let content = std::fs::read(&source).expect("read source");
6979 let size = content.len() as u64;
6980 let hash = crate::cache_freshness::hash_bytes(&content);
6981
6982 let mut cache = SymbolCache::new();
6983 cache.set_project_root(project.path().to_path_buf());
6984 cache.insert(
6985 source.clone(),
6986 mtime,
6987 size,
6988 hash,
6989 vec![test_symbol("cached")],
6990 );
6991 symbol_cache_disk::write_to_disk(&cache, storage.path(), "stale-unit-project")
6992 .expect("write symbol cache");
6993
6994 std::fs::write(&source, "pub fn cached() {}\npub fn fresh() {}\n").expect("change source");
6995
6996 let mut restored = SymbolCache::new();
6997 let loaded = restored.load_from_disk(storage.path(), "stale-unit-project", project.path());
6998
6999 assert_eq!(loaded, 0);
7000 assert_eq!(restored.len(), 0);
7001 }
7002
7003 #[test]
7004 fn stale_prewarm_generation_cannot_repopulate_symbol_cache_after_reset() {
7005 let project = tempfile::tempdir().expect("create project dir");
7006 let storage = tempfile::tempdir().expect("create storage dir");
7007 let source = project.path().join("src/lib.rs");
7008 std::fs::create_dir_all(source.parent().expect("source parent"))
7009 .expect("create source dir");
7010 std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
7011 let mtime = std::fs::metadata(&source)
7012 .expect("stat source")
7013 .modified()
7014 .expect("source mtime");
7015 let content = std::fs::read(&source).expect("read source");
7016 let size = content.len() as u64;
7017 let hash = crate::cache_freshness::hash_bytes(&content);
7018
7019 let mut disk_cache = SymbolCache::new();
7020 disk_cache.set_project_root(project.path().to_path_buf());
7021 disk_cache.insert(
7022 source.clone(),
7023 mtime,
7024 size,
7025 hash,
7026 vec![test_symbol("cached")],
7027 );
7028 symbol_cache_disk::write_to_disk(&disk_cache, storage.path(), "prewarm-reset")
7029 .expect("write symbol cache");
7030
7031 let shared = Arc::new(RwLock::new(SymbolCache::new()));
7032 let stale_generation = shared.write().unwrap().reset();
7033 let active_generation = shared.write().unwrap().reset();
7034 assert_ne!(stale_generation, active_generation);
7035
7036 {
7037 let mut cache = shared.write().unwrap();
7038 assert!(!cache
7039 .set_project_root_for_generation(stale_generation, project.path().to_path_buf()));
7040 assert_eq!(
7041 cache.load_from_disk_for_generation(
7042 stale_generation,
7043 storage.path(),
7044 "prewarm-reset",
7045 project.path()
7046 ),
7047 0
7048 );
7049 }
7050
7051 let mut stale_parser =
7052 FileParser::with_symbol_cache_generation(Arc::clone(&shared), Some(stale_generation));
7053 stale_parser
7054 .extract_symbols(&source)
7055 .expect("stale prewarm parses source but must not write cache");
7056
7057 let cache = shared.read().unwrap();
7058 assert_eq!(cache.generation(), active_generation);
7059 assert_eq!(cache.len(), 0);
7060 assert!(cache.project_root().is_none());
7061 assert!(!cache.contains_key(&source));
7062 }
7063
7064 #[test]
7067 fn detect_ts() {
7068 assert_eq!(
7069 detect_language(Path::new("foo.ts")),
7070 Some(LangId::TypeScript)
7071 );
7072 }
7073
7074 #[test]
7075 fn detect_tsx() {
7076 assert_eq!(detect_language(Path::new("foo.tsx")), Some(LangId::Tsx));
7077 }
7078
7079 #[test]
7080 fn detect_js() {
7081 assert_eq!(
7082 detect_language(Path::new("foo.js")),
7083 Some(LangId::JavaScript)
7084 );
7085 }
7086
7087 #[test]
7088 fn detect_jsx() {
7089 assert_eq!(
7090 detect_language(Path::new("foo.jsx")),
7091 Some(LangId::JavaScript)
7092 );
7093 }
7094
7095 #[test]
7096 fn detect_py() {
7097 assert_eq!(detect_language(Path::new("foo.py")), Some(LangId::Python));
7098 }
7099
7100 #[test]
7101 fn detect_rs() {
7102 assert_eq!(detect_language(Path::new("foo.rs")), Some(LangId::Rust));
7103 }
7104
7105 #[test]
7106 fn detect_go() {
7107 assert_eq!(detect_language(Path::new("foo.go")), Some(LangId::Go));
7108 }
7109
7110 #[test]
7111 fn detect_c() {
7112 assert_eq!(detect_language(Path::new("foo.c")), Some(LangId::C));
7113 }
7114
7115 #[test]
7116 fn detect_h() {
7117 assert_eq!(detect_language(Path::new("foo.h")), Some(LangId::C));
7118 }
7119
7120 #[test]
7121 fn detect_cc() {
7122 assert_eq!(detect_language(Path::new("foo.cc")), Some(LangId::Cpp));
7123 }
7124
7125 #[test]
7126 fn detect_cpp() {
7127 assert_eq!(detect_language(Path::new("foo.cpp")), Some(LangId::Cpp));
7128 }
7129
7130 #[test]
7131 fn detect_cxx() {
7132 assert_eq!(detect_language(Path::new("foo.cxx")), Some(LangId::Cpp));
7133 }
7134
7135 #[test]
7136 fn detect_hpp() {
7137 assert_eq!(detect_language(Path::new("foo.hpp")), Some(LangId::Cpp));
7138 }
7139
7140 #[test]
7141 fn detect_hh() {
7142 assert_eq!(detect_language(Path::new("foo.hh")), Some(LangId::Cpp));
7143 }
7144
7145 #[test]
7146 fn detect_zig() {
7147 assert_eq!(detect_language(Path::new("foo.zig")), Some(LangId::Zig));
7148 }
7149
7150 #[test]
7151 fn detect_cs() {
7152 assert_eq!(detect_language(Path::new("foo.cs")), Some(LangId::CSharp));
7153 }
7154
7155 #[test]
7156 fn detect_unknown_returns_none() {
7157 assert_eq!(detect_language(Path::new("foo.txt")), None);
7158 }
7159
7160 #[test]
7163 fn unsupported_extension_returns_invalid_request() {
7164 let path = fixture_path("sample.ts");
7166 let bad_path = path.with_extension("txt");
7167 std::fs::write(&bad_path, "hello").unwrap();
7169 let provider = TreeSitterProvider::new();
7170 let result = provider.list_symbols(&bad_path);
7171 std::fs::remove_file(&bad_path).ok();
7172 match result {
7173 Err(AftError::InvalidRequest { message }) => {
7174 assert!(
7175 message.contains("unsupported file extension"),
7176 "msg: {}",
7177 message
7178 );
7179 assert!(message.contains("txt"), "msg: {}", message);
7180 }
7181 other => panic!("expected InvalidRequest, got {:?}", other),
7182 }
7183 }
7184
7185 #[test]
7188 fn ts_extracts_all_symbol_kinds() {
7189 let provider = TreeSitterProvider::new();
7190 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
7191
7192 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
7193 assert!(
7194 names.contains(&"greet"),
7195 "missing function greet: {:?}",
7196 names
7197 );
7198 assert!(names.contains(&"add"), "missing arrow fn add: {:?}", names);
7199 assert!(
7200 names.contains(&"UserService"),
7201 "missing class UserService: {:?}",
7202 names
7203 );
7204 assert!(
7205 names.contains(&"Config"),
7206 "missing interface Config: {:?}",
7207 names
7208 );
7209 assert!(
7210 names.contains(&"Status"),
7211 "missing enum Status: {:?}",
7212 names
7213 );
7214 assert!(
7215 names.contains(&"UserId"),
7216 "missing type alias UserId: {:?}",
7217 names
7218 );
7219 assert!(
7220 names.contains(&"internalHelper"),
7221 "missing non-exported fn: {:?}",
7222 names
7223 );
7224
7225 assert!(
7227 symbols.len() >= 6,
7228 "expected ≥6 symbols, got {}: {:?}",
7229 symbols.len(),
7230 names
7231 );
7232 }
7233
7234 #[test]
7235 fn ts_symbol_kinds_correct() {
7236 let provider = TreeSitterProvider::new();
7237 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
7238
7239 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7240
7241 assert_eq!(find("greet").kind, SymbolKind::Function);
7242 assert_eq!(find("add").kind, SymbolKind::Function); assert_eq!(find("UserService").kind, SymbolKind::Class);
7244 assert_eq!(find("Config").kind, SymbolKind::Interface);
7245 assert_eq!(find("Status").kind, SymbolKind::Enum);
7246 assert_eq!(find("UserId").kind, SymbolKind::TypeAlias);
7247 }
7248
7249 #[test]
7250 fn ts_export_detection() {
7251 let provider = TreeSitterProvider::new();
7252 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
7253
7254 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7255
7256 assert!(find("greet").exported, "greet should be exported");
7257 assert!(find("add").exported, "add should be exported");
7258 assert!(
7259 find("UserService").exported,
7260 "UserService should be exported"
7261 );
7262 assert!(find("Config").exported, "Config should be exported");
7263 assert!(find("Status").exported, "Status should be exported");
7264 assert!(
7265 !find("internalHelper").exported,
7266 "internalHelper should not be exported"
7267 );
7268 }
7269
7270 #[test]
7271 fn ts_method_scope_chain() {
7272 let provider = TreeSitterProvider::new();
7273 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
7274
7275 let methods: Vec<&Symbol> = symbols
7276 .iter()
7277 .filter(|s| s.kind == SymbolKind::Method)
7278 .collect();
7279 assert!(!methods.is_empty(), "should have at least one method");
7280
7281 for method in &methods {
7282 assert_eq!(
7283 method.scope_chain,
7284 vec!["UserService"],
7285 "method {} should have UserService in scope chain",
7286 method.name
7287 );
7288 assert_eq!(method.parent.as_deref(), Some("UserService"));
7289 }
7290 }
7291
7292 #[test]
7293 fn ts_signatures_present() {
7294 let provider = TreeSitterProvider::new();
7295 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
7296
7297 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7298
7299 let greet_sig = find("greet").signature.as_ref().unwrap();
7300 assert!(
7301 greet_sig.contains("greet"),
7302 "signature should contain function name: {}",
7303 greet_sig
7304 );
7305 }
7306
7307 #[test]
7308 fn ts_ranges_valid() {
7309 let provider = TreeSitterProvider::new();
7310 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
7311
7312 for s in &symbols {
7313 assert!(
7314 s.range.end_line >= s.range.start_line,
7315 "symbol {} has invalid range: {:?}",
7316 s.name,
7317 s.range
7318 );
7319 }
7320 }
7321
7322 #[test]
7325 fn js_extracts_core_symbols() {
7326 let provider = TreeSitterProvider::new();
7327 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
7328
7329 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
7330 assert!(
7331 names.contains(&"multiply"),
7332 "missing function multiply: {:?}",
7333 names
7334 );
7335 assert!(
7336 names.contains(&"divide"),
7337 "missing arrow fn divide: {:?}",
7338 names
7339 );
7340 assert!(
7341 names.contains(&"EventEmitter"),
7342 "missing class EventEmitter: {:?}",
7343 names
7344 );
7345 assert!(
7346 names.contains(&"main"),
7347 "missing default export fn main: {:?}",
7348 names
7349 );
7350
7351 assert!(
7352 symbols.len() >= 4,
7353 "expected ≥4 symbols, got {}: {:?}",
7354 symbols.len(),
7355 names
7356 );
7357 }
7358
7359 #[test]
7360 fn js_arrow_fn_correctly_named() {
7361 let provider = TreeSitterProvider::new();
7362 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
7363
7364 let divide = symbols.iter().find(|s| s.name == "divide").unwrap();
7365 assert_eq!(divide.kind, SymbolKind::Function);
7366 assert!(divide.exported, "divide should be exported");
7367
7368 let internal = symbols.iter().find(|s| s.name == "internalUtil").unwrap();
7369 assert_eq!(internal.kind, SymbolKind::Function);
7370 assert!(!internal.exported, "internalUtil should not be exported");
7371 }
7372
7373 #[test]
7374 fn js_method_scope_chain() {
7375 let provider = TreeSitterProvider::new();
7376 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
7377
7378 let methods: Vec<&Symbol> = symbols
7379 .iter()
7380 .filter(|s| s.kind == SymbolKind::Method)
7381 .collect();
7382
7383 for method in &methods {
7384 assert_eq!(
7385 method.scope_chain,
7386 vec!["EventEmitter"],
7387 "method {} should have EventEmitter in scope chain",
7388 method.name
7389 );
7390 }
7391 }
7392
7393 #[test]
7396 fn tsx_extracts_react_component() {
7397 let provider = TreeSitterProvider::new();
7398 let symbols = provider.list_symbols(&fixture_path("sample.tsx")).unwrap();
7399
7400 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
7401 assert!(
7402 names.contains(&"Button"),
7403 "missing React component Button: {:?}",
7404 names
7405 );
7406 assert!(
7407 names.contains(&"Counter"),
7408 "missing class Counter: {:?}",
7409 names
7410 );
7411 assert!(
7412 names.contains(&"formatLabel"),
7413 "missing function formatLabel: {:?}",
7414 names
7415 );
7416
7417 assert!(
7418 symbols.len() >= 2,
7419 "expected ≥2 symbols, got {}: {:?}",
7420 symbols.len(),
7421 names
7422 );
7423 }
7424
7425 #[test]
7426 fn tsx_jsx_doesnt_break_parser() {
7427 let provider = TreeSitterProvider::new();
7429 let result = provider.list_symbols(&fixture_path("sample.tsx"));
7430 assert!(
7431 result.is_ok(),
7432 "TSX parsing should succeed: {:?}",
7433 result.err()
7434 );
7435 }
7436
7437 #[test]
7440 fn resolve_symbol_finds_match() {
7441 let provider = TreeSitterProvider::new();
7442 let matches = provider
7443 .resolve_symbol(&fixture_path("sample.ts"), "greet")
7444 .unwrap();
7445 assert_eq!(matches.len(), 1);
7446 assert_eq!(matches[0].symbol.name, "greet");
7447 assert_eq!(matches[0].symbol.kind, SymbolKind::Function);
7448 }
7449
7450 #[test]
7451 fn resolve_symbol_not_found() {
7452 let provider = TreeSitterProvider::new();
7453 let result = provider.resolve_symbol(&fixture_path("sample.ts"), "nonexistent");
7454 assert!(matches!(result, Err(AftError::SymbolNotFound { .. })));
7455 }
7456
7457 #[test]
7458 fn resolve_symbol_follows_reexport_chains() {
7459 let dir = tempfile::tempdir().unwrap();
7460 let config = dir.path().join("config.ts");
7461 let barrel1 = dir.path().join("barrel1.ts");
7462 let barrel2 = dir.path().join("barrel2.ts");
7463 let barrel3 = dir.path().join("barrel3.ts");
7464 let index = dir.path().join("index.ts");
7465
7466 std::fs::write(
7467 &config,
7468 "export class Config {}\nexport default class DefaultConfig {}\n",
7469 )
7470 .unwrap();
7471 std::fs::write(
7472 &barrel1,
7473 "export { Config } from './config';\nexport { default as NamedDefault } from './config';\n",
7474 )
7475 .unwrap();
7476 std::fs::write(
7477 &barrel2,
7478 "export { Config as RenamedConfig } from './barrel1';\n",
7479 )
7480 .unwrap();
7481 std::fs::write(
7482 &barrel3,
7483 "export * from './barrel2';\nexport * from './barrel1';\n",
7484 )
7485 .unwrap();
7486 std::fs::write(
7487 &index,
7488 "export class Config {}\nexport { RenamedConfig as FinalConfig } from './barrel3';\nexport * from './barrel3';\n",
7489 )
7490 .unwrap();
7491
7492 let provider = TreeSitterProvider::new();
7493 let config_canon = std::fs::canonicalize(&config).unwrap();
7494
7495 let direct = provider.resolve_symbol(&barrel1, "Config").unwrap();
7496 assert_eq!(direct.len(), 1);
7497 assert_eq!(direct[0].symbol.name, "Config");
7498 assert_eq!(direct[0].file, config_canon.display().to_string());
7499
7500 let renamed = provider.resolve_symbol(&barrel2, "RenamedConfig").unwrap();
7501 assert_eq!(renamed.len(), 1);
7502 assert_eq!(renamed[0].symbol.name, "Config");
7503 assert_eq!(renamed[0].file, config_canon.display().to_string());
7504
7505 let wildcard_chain = provider.resolve_symbol(&index, "FinalConfig").unwrap();
7506 assert_eq!(wildcard_chain.len(), 1);
7507 assert_eq!(wildcard_chain[0].symbol.name, "Config");
7508 assert_eq!(wildcard_chain[0].file, config_canon.display().to_string());
7509
7510 let wildcard_default = provider.resolve_symbol(&index, "NamedDefault").unwrap();
7511 assert_eq!(wildcard_default.len(), 1);
7512 assert_eq!(wildcard_default[0].symbol.name, "DefaultConfig");
7513 assert_eq!(wildcard_default[0].file, config_canon.display().to_string());
7514
7515 let local = provider.resolve_symbol(&index, "Config").unwrap();
7516 assert_eq!(local.len(), 1);
7517 assert_eq!(local[0].symbol.name, "Config");
7518 assert_eq!(local[0].file, index.display().to_string());
7519 }
7520
7521 #[test]
7524 fn symbol_range_includes_rust_attributes() {
7525 let dir = tempfile::tempdir().unwrap();
7526 let path = dir.path().join("test_attrs.rs");
7527 std::fs::write(
7528 &path,
7529 "/// This is a doc comment\n#[test]\n#[cfg(test)]\nfn my_test_fn() {\n assert!(true);\n}\n",
7530 )
7531 .unwrap();
7532
7533 let provider = TreeSitterProvider::new();
7534 let matches = provider.resolve_symbol(&path, "my_test_fn").unwrap();
7535 assert_eq!(matches.len(), 1);
7536 assert_eq!(
7537 matches[0].symbol.range.start_line, 0,
7538 "symbol range should include preceding /// doc comment, got start_line={}",
7539 matches[0].symbol.range.start_line
7540 );
7541 }
7542
7543 #[test]
7544 fn symbol_range_includes_go_doc_comment() {
7545 let dir = tempfile::tempdir().unwrap();
7546 let path = dir.path().join("test_doc.go");
7547 std::fs::write(
7548 &path,
7549 "package main\n\n// MyFunc does something useful.\n// It has a multi-line doc.\nfunc MyFunc() {\n}\n",
7550 )
7551 .unwrap();
7552
7553 let provider = TreeSitterProvider::new();
7554 let matches = provider.resolve_symbol(&path, "MyFunc").unwrap();
7555 assert_eq!(matches.len(), 1);
7556 assert_eq!(
7557 matches[0].symbol.range.start_line, 2,
7558 "symbol range should include preceding doc comments, got start_line={}",
7559 matches[0].symbol.range.start_line
7560 );
7561 }
7562
7563 #[test]
7564 fn symbol_range_skips_unrelated_comments() {
7565 let dir = tempfile::tempdir().unwrap();
7566 let path = dir.path().join("test_gap.go");
7567 std::fs::write(
7568 &path,
7569 "package main\n\n// This is a standalone comment\n\nfunc Standalone() {\n}\n",
7570 )
7571 .unwrap();
7572
7573 let provider = TreeSitterProvider::new();
7574 let matches = provider.resolve_symbol(&path, "Standalone").unwrap();
7575 assert_eq!(matches.len(), 1);
7576 assert_eq!(
7577 matches[0].symbol.range.start_line, 4,
7578 "symbol range should NOT include comment separated by blank line, got start_line={}",
7579 matches[0].symbol.range.start_line
7580 );
7581 }
7582
7583 #[test]
7584 fn parse_cache_returns_same_tree() {
7585 let mut parser = FileParser::new();
7586 let path = fixture_path("sample.ts");
7587
7588 let (tree1, _) = parser.parse(&path).unwrap();
7589 let tree1_root = tree1.root_node().byte_range();
7590
7591 let (tree2, _) = parser.parse(&path).unwrap();
7592 let tree2_root = tree2.root_node().byte_range();
7593
7594 assert_eq!(tree1_root, tree2_root);
7596 }
7597
7598 #[test]
7599 fn extract_symbols_from_tree_matches_list_symbols() {
7600 let path = fixture_path("sample.rs");
7601 let source = std::fs::read_to_string(&path).unwrap();
7602
7603 let provider = TreeSitterProvider::new();
7604 let listed = provider.list_symbols(&path).unwrap();
7605
7606 let mut parser = FileParser::new();
7607 let (tree, lang) = parser.parse(&path).unwrap();
7608 let extracted = extract_symbols_from_tree(&source, tree, lang).unwrap();
7609
7610 assert_eq!(symbols_as_debug(&extracted), symbols_as_debug(&listed));
7611 }
7612
7613 fn symbols_as_debug(symbols: &[Symbol]) -> Vec<String> {
7614 symbols
7615 .iter()
7616 .map(|symbol| {
7617 format!(
7618 "{}|{:?}|{}:{}-{}:{}|{:?}|{:?}|{}|{:?}",
7619 symbol.name,
7620 symbol.kind,
7621 symbol.range.start_line,
7622 symbol.range.start_col,
7623 symbol.range.end_line,
7624 symbol.range.end_col,
7625 symbol.signature,
7626 symbol.scope_chain,
7627 symbol.exported,
7628 symbol.parent,
7629 )
7630 })
7631 .collect()
7632 }
7633
7634 #[test]
7637 fn py_extracts_all_symbols() {
7638 let provider = TreeSitterProvider::new();
7639 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
7640
7641 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
7642 assert!(
7643 names.contains(&"top_level_function"),
7644 "missing top_level_function: {:?}",
7645 names
7646 );
7647 assert!(names.contains(&"MyClass"), "missing MyClass: {:?}", names);
7648 assert!(
7649 names.contains(&"instance_method"),
7650 "missing method instance_method: {:?}",
7651 names
7652 );
7653 assert!(
7654 names.contains(&"decorated_function"),
7655 "missing decorated_function: {:?}",
7656 names
7657 );
7658
7659 assert!(
7661 symbols.len() >= 4,
7662 "expected ≥4 symbols, got {}: {:?}",
7663 symbols.len(),
7664 names
7665 );
7666 }
7667
7668 #[test]
7669 fn py_symbol_kinds_correct() {
7670 let provider = TreeSitterProvider::new();
7671 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
7672
7673 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7674
7675 assert_eq!(find("top_level_function").kind, SymbolKind::Function);
7676 assert_eq!(find("MyClass").kind, SymbolKind::Class);
7677 assert_eq!(find("instance_method").kind, SymbolKind::Method);
7678 assert_eq!(find("decorated_function").kind, SymbolKind::Function);
7679 assert_eq!(find("OuterClass").kind, SymbolKind::Class);
7680 assert_eq!(find("InnerClass").kind, SymbolKind::Class);
7681 assert_eq!(find("inner_method").kind, SymbolKind::Method);
7682 assert_eq!(find("outer_method").kind, SymbolKind::Method);
7683 }
7684
7685 #[test]
7686 fn py_method_scope_chain() {
7687 let provider = TreeSitterProvider::new();
7688 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
7689
7690 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7691
7692 assert_eq!(
7694 find("instance_method").scope_chain,
7695 vec!["MyClass"],
7696 "instance_method should have MyClass in scope chain"
7697 );
7698 assert_eq!(find("instance_method").parent.as_deref(), Some("MyClass"));
7699
7700 assert_eq!(
7702 find("inner_method").scope_chain,
7703 vec!["OuterClass", "InnerClass"],
7704 "inner_method should have nested scope chain"
7705 );
7706
7707 assert_eq!(
7709 find("InnerClass").scope_chain,
7710 vec!["OuterClass"],
7711 "InnerClass should have OuterClass in scope"
7712 );
7713
7714 assert!(
7716 find("top_level_function").scope_chain.is_empty(),
7717 "top-level function should have empty scope chain"
7718 );
7719 }
7720
7721 #[test]
7722 fn py_decorated_function_signature() {
7723 let provider = TreeSitterProvider::new();
7724 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
7725
7726 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7727
7728 let sig = find("decorated_function").signature.as_ref().unwrap();
7729 assert!(
7730 sig.contains("@staticmethod"),
7731 "decorated function signature should include decorator: {}",
7732 sig
7733 );
7734 assert!(
7735 sig.contains("def decorated_function"),
7736 "signature should include function def: {}",
7737 sig
7738 );
7739 }
7740
7741 #[test]
7742 fn py_ranges_valid() {
7743 let provider = TreeSitterProvider::new();
7744 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
7745
7746 for s in &symbols {
7747 assert!(
7748 s.range.end_line >= s.range.start_line,
7749 "symbol {} has invalid range: {:?}",
7750 s.name,
7751 s.range
7752 );
7753 }
7754 }
7755
7756 #[test]
7759 fn rs_extracts_all_symbols() {
7760 let provider = TreeSitterProvider::new();
7761 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
7762
7763 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
7764 assert!(
7765 names.contains(&"public_function"),
7766 "missing public_function: {:?}",
7767 names
7768 );
7769 assert!(
7770 names.contains(&"private_function"),
7771 "missing private_function: {:?}",
7772 names
7773 );
7774 assert!(names.contains(&"MyStruct"), "missing MyStruct: {:?}", names);
7775 assert!(names.contains(&"Color"), "missing enum Color: {:?}", names);
7776 assert!(
7777 names.contains(&"Drawable"),
7778 "missing trait Drawable: {:?}",
7779 names
7780 );
7781 assert!(
7783 names.contains(&"new"),
7784 "missing impl method new: {:?}",
7785 names
7786 );
7787
7788 assert!(
7790 symbols.len() >= 6,
7791 "expected ≥6 symbols, got {}: {:?}",
7792 symbols.len(),
7793 names
7794 );
7795 }
7796
7797 #[test]
7798 fn rs_symbol_kinds_correct() {
7799 let provider = TreeSitterProvider::new();
7800 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
7801
7802 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7803
7804 assert_eq!(find("public_function").kind, SymbolKind::Function);
7805 assert_eq!(find("private_function").kind, SymbolKind::Function);
7806 assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
7807 assert_eq!(find("Color").kind, SymbolKind::Enum);
7808 assert_eq!(find("Drawable").kind, SymbolKind::Interface); assert_eq!(find("new").kind, SymbolKind::Method);
7810 }
7811
7812 #[test]
7813 fn rs_pub_export_detection() {
7814 let provider = TreeSitterProvider::new();
7815 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
7816
7817 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7818
7819 assert!(
7820 find("public_function").exported,
7821 "pub fn should be exported"
7822 );
7823 assert!(
7824 !find("private_function").exported,
7825 "non-pub fn should not be exported"
7826 );
7827 assert!(find("MyStruct").exported, "pub struct should be exported");
7828 assert!(find("Color").exported, "pub enum should be exported");
7829 assert!(find("Drawable").exported, "pub trait should be exported");
7830 assert!(
7831 find("new").exported,
7832 "pub fn inside impl should be exported"
7833 );
7834 assert!(
7835 !find("helper").exported,
7836 "non-pub fn inside impl should not be exported"
7837 );
7838 }
7839
7840 #[test]
7841 fn rs_impl_method_scope_chain() {
7842 let provider = TreeSitterProvider::new();
7843 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
7844
7845 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7846
7847 assert_eq!(
7849 find("new").scope_chain,
7850 vec!["MyStruct"],
7851 "impl method should have type in scope chain"
7852 );
7853 assert_eq!(find("new").parent.as_deref(), Some("MyStruct"));
7854
7855 assert!(
7857 find("public_function").scope_chain.is_empty(),
7858 "free function should have empty scope chain"
7859 );
7860 }
7861
7862 #[test]
7863 fn rs_trait_impl_scope_chain() {
7864 let provider = TreeSitterProvider::new();
7865 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
7866
7867 let draw = symbols.iter().find(|s| s.name == "draw").unwrap();
7869 assert_eq!(
7870 draw.scope_chain,
7871 vec!["Drawable for MyStruct"],
7872 "trait impl method should have 'Trait for Type' scope"
7873 );
7874 assert_eq!(draw.parent.as_deref(), Some("MyStruct"));
7875 }
7876
7877 #[test]
7878 fn rs_ranges_valid() {
7879 let provider = TreeSitterProvider::new();
7880 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
7881
7882 for s in &symbols {
7883 assert!(
7884 s.range.end_line >= s.range.start_line,
7885 "symbol {} has invalid range: {:?}",
7886 s.name,
7887 s.range
7888 );
7889 }
7890 }
7891
7892 #[test]
7895 fn go_extracts_all_symbols() {
7896 let provider = TreeSitterProvider::new();
7897 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
7898
7899 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
7900 assert!(
7901 names.contains(&"ExportedFunction"),
7902 "missing ExportedFunction: {:?}",
7903 names
7904 );
7905 assert!(
7906 names.contains(&"unexportedFunction"),
7907 "missing unexportedFunction: {:?}",
7908 names
7909 );
7910 assert!(
7911 names.contains(&"MyStruct"),
7912 "missing struct MyStruct: {:?}",
7913 names
7914 );
7915 assert!(
7916 names.contains(&"Reader"),
7917 "missing interface Reader: {:?}",
7918 names
7919 );
7920 assert!(
7922 names.contains(&"String"),
7923 "missing receiver method String: {:?}",
7924 names
7925 );
7926
7927 assert!(
7929 symbols.len() >= 4,
7930 "expected ≥4 symbols, got {}: {:?}",
7931 symbols.len(),
7932 names
7933 );
7934 }
7935
7936 #[test]
7937 fn go_symbol_kinds_correct() {
7938 let provider = TreeSitterProvider::new();
7939 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
7940
7941 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7942
7943 assert_eq!(find("ExportedFunction").kind, SymbolKind::Function);
7944 assert_eq!(find("unexportedFunction").kind, SymbolKind::Function);
7945 assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
7946 assert_eq!(find("Reader").kind, SymbolKind::Interface);
7947 assert_eq!(find("String").kind, SymbolKind::Method);
7948 assert_eq!(find("helper").kind, SymbolKind::Method);
7949 }
7950
7951 #[test]
7952 fn go_uppercase_export_detection() {
7953 let provider = TreeSitterProvider::new();
7954 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
7955
7956 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7957
7958 assert!(
7959 find("ExportedFunction").exported,
7960 "ExportedFunction (uppercase) should be exported"
7961 );
7962 assert!(
7963 !find("unexportedFunction").exported,
7964 "unexportedFunction (lowercase) should not be exported"
7965 );
7966 assert!(
7967 find("MyStruct").exported,
7968 "MyStruct (uppercase) should be exported"
7969 );
7970 assert!(
7971 find("Reader").exported,
7972 "Reader (uppercase) should be exported"
7973 );
7974 assert!(
7975 find("String").exported,
7976 "String method (uppercase) should be exported"
7977 );
7978 assert!(
7979 !find("helper").exported,
7980 "helper method (lowercase) should not be exported"
7981 );
7982 }
7983
7984 #[test]
7985 fn go_receiver_method_scope_chain() {
7986 let provider = TreeSitterProvider::new();
7987 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
7988
7989 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7990
7991 assert_eq!(
7993 find("String").scope_chain,
7994 vec!["MyStruct"],
7995 "receiver method should have type in scope chain"
7996 );
7997 assert_eq!(find("String").parent.as_deref(), Some("MyStruct"));
7998
7999 assert!(
8001 find("ExportedFunction").scope_chain.is_empty(),
8002 "regular function should have empty scope chain"
8003 );
8004 }
8005
8006 #[test]
8007 fn go_ranges_valid() {
8008 let provider = TreeSitterProvider::new();
8009 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
8010
8011 for s in &symbols {
8012 assert!(
8013 s.range.end_line >= s.range.start_line,
8014 "symbol {} has invalid range: {:?}",
8015 s.name,
8016 s.range
8017 );
8018 }
8019 }
8020
8021 #[test]
8024 fn cross_language_all_six_produce_symbols() {
8025 let provider = TreeSitterProvider::new();
8026
8027 let fixtures = [
8028 ("sample.ts", "TypeScript"),
8029 ("sample.tsx", "TSX"),
8030 ("sample.js", "JavaScript"),
8031 ("sample.py", "Python"),
8032 ("sample.rs", "Rust"),
8033 ("sample.go", "Go"),
8034 ];
8035
8036 for (fixture, lang) in &fixtures {
8037 let symbols = provider
8038 .list_symbols(&fixture_path(fixture))
8039 .unwrap_or_else(|e| panic!("{} ({}) failed: {:?}", lang, fixture, e));
8040 assert!(
8041 symbols.len() >= 2,
8042 "{} should produce ≥2 symbols, got {}: {:?}",
8043 lang,
8044 symbols.len(),
8045 symbols.iter().map(|s| &s.name).collect::<Vec<_>>()
8046 );
8047 }
8048 }
8049
8050 #[test]
8053 fn symbol_cache_returns_cached_results_on_second_call() {
8054 let dir = tempfile::tempdir().unwrap();
8055 let file = dir.path().join("test.rs");
8056 std::fs::write(&file, "pub fn hello() {}\npub fn world() {}").unwrap();
8057
8058 let mut parser = FileParser::new();
8059
8060 let symbols1 = parser.extract_symbols(&file).unwrap();
8061 assert_eq!(symbols1.len(), 2);
8062
8063 let symbols2 = parser.extract_symbols(&file).unwrap();
8065 assert_eq!(symbols2.len(), 2);
8066 assert_eq!(symbols1[0].name, symbols2[0].name);
8067
8068 assert!(parser.symbol_cache.read().unwrap().contains_key(&file));
8070 }
8071
8072 #[test]
8073 fn symbol_cache_invalidates_on_file_change() {
8074 let dir = tempfile::tempdir().unwrap();
8075 let file = dir.path().join("test.rs");
8076 std::fs::write(&file, "pub fn hello() {}").unwrap();
8077
8078 let mut parser = FileParser::new();
8079
8080 let symbols1 = parser.extract_symbols(&file).unwrap();
8081 assert_eq!(symbols1.len(), 1);
8082 assert_eq!(symbols1[0].name, "hello");
8083
8084 std::thread::sleep(std::time::Duration::from_millis(50));
8086
8087 std::fs::write(&file, "pub fn hello() {}\npub fn goodbye() {}").unwrap();
8089
8090 let symbols2 = parser.extract_symbols(&file).unwrap();
8092 assert_eq!(symbols2.len(), 2);
8093 assert!(symbols2.iter().any(|s| s.name == "goodbye"));
8094 }
8095
8096 #[test]
8097 fn symbol_cache_invalidate_method_clears_entry() {
8098 let dir = tempfile::tempdir().unwrap();
8099 let file = dir.path().join("test.rs");
8100 std::fs::write(&file, "pub fn hello() {}").unwrap();
8101
8102 let mut parser = FileParser::new();
8103 parser.extract_symbols(&file).unwrap();
8104 assert!(parser.symbol_cache.read().unwrap().contains_key(&file));
8105
8106 parser.invalidate_symbols(&file);
8107 assert!(!parser.symbol_cache.read().unwrap().contains_key(&file));
8108 assert!(!parser.cache.contains_key(&file));
8110 }
8111
8112 #[test]
8113 fn symbol_cache_works_for_multiple_languages() {
8114 let dir = tempfile::tempdir().unwrap();
8115 let rs_file = dir.path().join("lib.rs");
8116 let ts_file = dir.path().join("app.ts");
8117 let py_file = dir.path().join("main.py");
8118
8119 std::fs::write(&rs_file, "pub fn rust_fn() {}").unwrap();
8120 std::fs::write(&ts_file, "export function tsFn() {}").unwrap();
8121 std::fs::write(&py_file, "def py_fn():\n pass").unwrap();
8122
8123 let mut parser = FileParser::new();
8124
8125 let rs_syms = parser.extract_symbols(&rs_file).unwrap();
8126 let ts_syms = parser.extract_symbols(&ts_file).unwrap();
8127 let py_syms = parser.extract_symbols(&py_file).unwrap();
8128
8129 assert!(rs_syms.iter().any(|s| s.name == "rust_fn"));
8130 assert!(ts_syms.iter().any(|s| s.name == "tsFn"));
8131 assert!(py_syms.iter().any(|s| s.name == "py_fn"));
8132
8133 assert_eq!(parser.symbol_cache.read().unwrap().len(), 3);
8135
8136 let rs_syms2 = parser.extract_symbols(&rs_file).unwrap();
8138 assert_eq!(rs_syms.len(), rs_syms2.len());
8139 }
8140
8141 #[test]
8142 fn extract_json_symbols_top_level_keys() {
8143 let dir = tempfile::tempdir().unwrap();
8144 let file = dir.path().join("package.json");
8145 std::fs::write(&file, r#"{"name": "x", "version": "1"}"#).unwrap();
8146
8147 let mut parser = FileParser::new();
8148 let symbols = parser.extract_symbols(&file).unwrap();
8149
8150 assert_eq!(symbols.len(), 2);
8151 assert!(symbols
8152 .iter()
8153 .any(|s| s.name == "name" && s.kind == SymbolKind::Variable));
8154 assert!(symbols
8155 .iter()
8156 .any(|s| s.name == "version" && s.kind == SymbolKind::Variable));
8157 }
8158
8159 #[test]
8160 fn extract_json_symbols_root_array() {
8161 let dir = tempfile::tempdir().unwrap();
8162 let file = dir.path().join("array.json");
8163 std::fs::write(&file, "[1,2,3]").unwrap();
8164
8165 let mut parser = FileParser::new();
8166 let symbols = parser.extract_symbols(&file).unwrap();
8167
8168 assert_eq!(symbols.len(), 0);
8169 }
8170
8171 #[test]
8172 fn extract_json_symbols_no_recursion_into_nested() {
8173 let dir = tempfile::tempdir().unwrap();
8174 let file = dir.path().join("nested.json");
8175 std::fs::write(&file, r#"{"scripts": {"build": "tsc"}}"#).unwrap();
8176
8177 let mut parser = FileParser::new();
8178 let symbols = parser.extract_symbols(&file).unwrap();
8179
8180 assert_eq!(symbols.len(), 1);
8181 assert_eq!(symbols[0].name, "scripts");
8182 assert!(!symbols.iter().any(|s| s.name == "build"));
8183 }
8184
8185 #[test]
8186 fn extract_scala_symbols_object_and_method() {
8187 let dir = tempfile::tempdir().unwrap();
8188 let file = dir.path().join("Greeter.scala");
8189 std::fs::write(
8190 &file,
8191 "object Greeter {\n def hello(name: String): String = s\"hi $name\"\n}",
8192 )
8193 .unwrap();
8194
8195 let mut parser = FileParser::new();
8196 let symbols = parser.extract_symbols(&file).unwrap();
8197
8198 assert!(symbols
8199 .iter()
8200 .any(|s| s.name == "Greeter" && s.kind == SymbolKind::Class));
8201 assert!(symbols.iter().any(|s| s.name == "hello"
8202 && s.kind == SymbolKind::Method
8203 && s.scope_chain == vec!["Greeter".to_string()]));
8204 }
8205
8206 #[test]
8207 fn extract_scala_symbols_class_and_trait() {
8208 let dir = tempfile::tempdir().unwrap();
8209 let file = dir.path().join("Types.scala");
8210 std::fs::write(&file, "class Foo\ntrait Bar").unwrap();
8211
8212 let mut parser = FileParser::new();
8213 let symbols = parser.extract_symbols(&file).unwrap();
8214
8215 assert!(symbols
8216 .iter()
8217 .any(|s| s.name == "Foo" && s.kind == SymbolKind::Class));
8218 assert!(symbols
8219 .iter()
8220 .any(|s| s.name == "Bar" && s.kind == SymbolKind::Interface));
8221 }
8222
8223 #[test]
8224 fn extract_yaml_symbols_k8s_resource() {
8225 let dir = tempfile::tempdir().unwrap();
8226 let file = dir.path().join("deployment.yaml");
8227 std::fs::write(
8228 &file,
8229 r#"apiVersion: apps/v1
8230kind: Deployment
8231metadata:
8232 name: nginx
8233 namespace: web
8234"#,
8235 )
8236 .unwrap();
8237
8238 let mut parser = FileParser::new();
8239 let symbols = parser.extract_symbols(&file).unwrap();
8240
8241 assert_eq!(symbols.len(), 1, "Expected 1 symbol for K8s Deployment");
8242 let sym = &symbols[0];
8243 assert_eq!(sym.name, "web/Deployment/nginx");
8244 assert_eq!(sym.kind, SymbolKind::Class);
8245 assert!(sym.exported);
8246 }
8247
8248 #[test]
8249 fn extract_yaml_symbols_k8s_no_namespace() {
8250 let dir = tempfile::tempdir().unwrap();
8251 let file = dir.path().join("service.yaml");
8252 std::fs::write(
8253 &file,
8254 r#"apiVersion: v1
8255kind: Service
8256metadata:
8257 name: nginx-svc
8258"#,
8259 )
8260 .unwrap();
8261
8262 let mut parser = FileParser::new();
8263 let symbols = parser.extract_symbols(&file).unwrap();
8264
8265 assert_eq!(symbols.len(), 1, "Expected 1 symbol for K8s Service");
8266 let sym = &symbols[0];
8267 assert_eq!(sym.name, "Service/nginx-svc");
8268 assert_eq!(sym.kind, SymbolKind::Class);
8269 }
8270
8271 #[test]
8272 fn extract_yaml_symbols_multidoc() {
8273 let dir = tempfile::tempdir().unwrap();
8274 let file = dir.path().join("multidoc.yaml");
8275 std::fs::write(
8276 &file,
8277 r#"apiVersion: apps/v1
8278kind: Deployment
8279metadata:
8280 name: a
8281---
8282apiVersion: v1
8283kind: Service
8284metadata:
8285 name: b
8286"#,
8287 )
8288 .unwrap();
8289
8290 let mut parser = FileParser::new();
8291 let symbols = parser.extract_symbols(&file).unwrap();
8292
8293 assert_eq!(symbols.len(), 2, "Expected 2 symbols for multi-doc YAML");
8294 assert!(symbols.iter().any(|s| s.name == "Deployment/a"));
8295 assert!(symbols.iter().any(|s| s.name == "Service/b"));
8296 }
8297
8298 #[test]
8299 fn extract_yaml_symbols_generic_fallback() {
8300 let dir = tempfile::tempdir().unwrap();
8301 let file = dir.path().join("compose.yaml");
8302 std::fs::write(
8303 &file,
8304 r#"version: "3"
8305services:
8306 web: {}
8307volumes:
8308 data: {}
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(), 3, "Expected 3 symbols for generic YAML");
8318 assert!(symbols
8319 .iter()
8320 .any(|s| s.name == "version" && s.kind == SymbolKind::Variable));
8321 assert!(symbols
8322 .iter()
8323 .any(|s| s.name == "services" && s.kind == SymbolKind::Variable));
8324 assert!(symbols
8325 .iter()
8326 .any(|s| s.name == "volumes" && s.kind == SymbolKind::Variable));
8327 }
8328
8329 #[test]
8330 fn extract_yaml_symbols_empty() {
8331 let dir = tempfile::tempdir().unwrap();
8332 let file = dir.path().join("empty.yaml");
8333 std::fs::write(&file, "").unwrap();
8334
8335 let mut parser = FileParser::new();
8336 let symbols = parser.extract_symbols(&file).unwrap();
8337
8338 assert_eq!(symbols.len(), 0, "Expected 0 symbols for empty YAML");
8339 }
8340
8341 #[test]
8342 fn extract_yaml_symbols_resource_limits() {
8343 let dir = tempfile::tempdir().unwrap();
8344 let file = dir.path().join("deployment.yaml");
8345 std::fs::write(
8346 &file,
8347 r#"apiVersion: apps/v1
8348kind: Deployment
8349metadata:
8350 name: app
8351spec:
8352 template:
8353 spec:
8354 containers:
8355 - name: main
8356 image: myapp:1.0
8357 resources:
8358 limits:
8359 cpu: "2"
8360 memory: 1Gi
8361 requests:
8362 cpu: "1"
8363 memory: 512Mi
8364"#,
8365 )
8366 .unwrap();
8367
8368 let mut parser = FileParser::new();
8369 let symbols = parser.extract_symbols(&file).unwrap();
8370
8371 assert_eq!(symbols.len(), 1, "Expected 1 symbol for Deployment");
8372 let sym = &symbols[0];
8373 assert_eq!(sym.name, "Deployment/app");
8374 let sig = sym.signature.as_ref().unwrap();
8375 assert!(sig.contains("cpu="), "Signature should contain cpu= field");
8376 assert!(
8377 sig.contains("memory="),
8378 "Signature should contain memory= field"
8379 );
8380 }
8381
8382 #[test]
8383 fn extract_yaml_symbols_env_names() {
8384 let dir = tempfile::tempdir().unwrap();
8385 let file = dir.path().join("deployment.yaml");
8386 std::fs::write(
8387 &file,
8388 r#"apiVersion: apps/v1
8389kind: Deployment
8390metadata:
8391 name: app
8392spec:
8393 template:
8394 spec:
8395 containers:
8396 - name: main
8397 image: myapp:1.0
8398 env:
8399 - name: FOO
8400 value: "bar"
8401 - name: BAR
8402 value: "baz"
8403"#,
8404 )
8405 .unwrap();
8406
8407 let mut parser = FileParser::new();
8408 let symbols = parser.extract_symbols(&file).unwrap();
8409
8410 assert_eq!(symbols.len(), 1, "Expected 1 symbol for Deployment");
8411 let sym = &symbols[0];
8412 let sig = sym.signature.as_ref().unwrap();
8413 assert!(
8414 sig.contains("env=FOO,BAR"),
8415 "Signature should contain env=FOO,BAR"
8416 );
8417 }
8418
8419 #[test]
8420 fn extract_yaml_symbols_rbac_rules() {
8421 let dir = tempfile::tempdir().unwrap();
8422 let file = dir.path().join("role.yaml");
8423 std::fs::write(
8424 &file,
8425 r#"apiVersion: rbac.authorization.k8s.io/v1
8426kind: Role
8427metadata:
8428 name: reader
8429rules:
8430- apiGroups: [""]
8431 resources: [pods, services]
8432 verbs: [get, list, watch]
8433"#,
8434 )
8435 .unwrap();
8436
8437 let mut parser = FileParser::new();
8438 let symbols = parser.extract_symbols(&file).unwrap();
8439
8440 assert_eq!(symbols.len(), 1, "Expected 1 symbol for Role");
8441 let sym = &symbols[0];
8442 let sig = sym.signature.as_ref().unwrap();
8443 assert!(
8444 sig.contains("verbs=get,list,watch"),
8445 "Signature should contain verbs=get,list,watch"
8446 );
8447 assert!(
8448 sig.contains("resources=pods,services"),
8449 "Signature should contain resources=pods,services"
8450 );
8451 }
8452
8453 #[test]
8454 fn extract_yaml_symbols_argo_workflow() {
8455 let dir = tempfile::tempdir().unwrap();
8456 let file = dir.path().join("workflow.yaml");
8457 std::fs::write(
8458 &file,
8459 r#"apiVersion: argoproj.io/v1alpha1
8460kind: Workflow
8461metadata:
8462 name: hello-world
8463spec:
8464 entrypoint: main
8465 templates:
8466 - name: main
8467 container:
8468 image: alpine:3.18
8469 command: [echo]
8470 args: ["hello"]
8471 - name: print
8472 container:
8473 image: alpine:3.18
8474 command: [echo]
8475 args: ["world"]
8476"#,
8477 )
8478 .unwrap();
8479
8480 let mut parser = FileParser::new();
8481 let symbols = parser.extract_symbols(&file).unwrap();
8482
8483 assert_eq!(symbols.len(), 1, "Expected 1 symbol for Workflow");
8484 let sym = &symbols[0];
8485 assert!(
8486 sym.name.contains("Workflow"),
8487 "Symbol name should contain Workflow"
8488 );
8489 let sig = sym.signature.as_ref().unwrap();
8490 assert!(
8491 sig.contains("entrypoint=main"),
8492 "Signature should contain entrypoint=main"
8493 );
8494 assert!(
8495 sig.contains("templates=main,print"),
8496 "Signature should contain templates=main,print"
8497 );
8498 assert!(
8499 sig.contains("image=alpine:3.18"),
8500 "Signature should contain image=alpine:3.18"
8501 );
8502 assert!(
8503 sig.contains("command=echo"),
8504 "Signature should contain command=echo"
8505 );
8506 }
8507
8508 #[test]
8509 fn extract_yaml_symbols_generatename_fallback() {
8510 let dir = tempfile::tempdir().unwrap();
8511 let file = dir.path().join("workflow.yaml");
8512 std::fs::write(
8513 &file,
8514 r#"apiVersion: argoproj.io/v1alpha1
8515kind: Workflow
8516metadata:
8517 generateName: hello-
8518spec:
8519 entrypoint: main
8520 templates:
8521 - name: main
8522 container:
8523 image: alpine:3.18
8524"#,
8525 )
8526 .unwrap();
8527
8528 let mut parser = FileParser::new();
8529 let symbols = parser.extract_symbols(&file).unwrap();
8530
8531 assert_eq!(symbols.len(), 1, "Expected 1 symbol for Workflow");
8532 let sym = &symbols[0];
8533 assert!(
8534 sym.name.contains("hello-"),
8535 "Symbol name should contain generateName fallback 'hello-'"
8536 );
8537 }
8538
8539 #[test]
8540 fn detect_r_extensions_are_case_sensitive() {
8541 assert_eq!(detect_language(Path::new("analysis.R")), Some(LangId::R));
8542 assert_eq!(detect_language(Path::new("script.r")), Some(LangId::R));
8543 }
8544
8545 #[test]
8546 fn extract_r_symbols_test() {
8547 let dir = tempfile::tempdir().unwrap();
8548 let file = dir.path().join("analysis.R");
8549 std::fs::write(
8550 &file,
8551 r#"
8552# summary function
8553summarise <- function(data, column) {
8554 total <- sum(data[[column]])
8555 total
8556}
8557
8558normalise = function(x) {
8559 x / max(x)
8560}
8561
8562function(y) {
8563 y + 1
8564} -> transform_values
8565
8566threshold <- 10
8567"done" -> status
8568
8569outer <- function(values) {
8570 local_value <- 1
8571 local_value
8572}
8573"#,
8574 )
8575 .unwrap();
8576
8577 let mut parser = FileParser::new();
8578 let symbols = parser.extract_symbols(&file).unwrap();
8579
8580 let get = |name: &str| {
8581 symbols
8582 .iter()
8583 .find(|symbol| symbol.name == name)
8584 .unwrap_or_else(|| panic!("missing {name}; got {symbols:?}"))
8585 };
8586
8587 assert_eq!(get("summarise").kind, SymbolKind::Function);
8588 assert_eq!(get("normalise").kind, SymbolKind::Function);
8589 assert_eq!(get("transform_values").kind, SymbolKind::Function);
8590 assert!(
8591 symbols
8592 .iter()
8593 .all(|symbol| !symbol.name.starts_with("function(")),
8594 "rightward function assignments should use the RHS identifier: {symbols:?}"
8595 );
8596 assert_eq!(get("outer").kind, SymbolKind::Function);
8597 assert_eq!(get("threshold").kind, SymbolKind::Variable);
8598 assert_eq!(get("status").kind, SymbolKind::Variable);
8599 assert!(
8600 symbols.iter().all(|symbol| symbol.name != "local_value"),
8601 "nested assignments should not surface as top-level variables: {symbols:?}"
8602 );
8603 }
8604
8605 #[test]
8606 fn extract_pascal_symbols_test() {
8607 let dir = tempfile::tempdir().unwrap();
8608 let file = dir.path().join("MyUnit.pas");
8609 std::fs::write(
8610 &file,
8611 r#"
8612unit MyUnit;
8613
8614interface
8615
8616uses SysUtils;
8617
8618type
8619 TMyClass = class
8620 private
8621 FValue: Integer;
8622 public
8623 constructor Create;
8624 procedure DoSomething; virtual;
8625 end;
8626
8627 TMyRecord = record
8628 X, Y: Integer;
8629 end;
8630
8631 IMyInterface = interface
8632 procedure DoSomethingElse;
8633 end;
8634
8635 TMyEnum = (Red, Green, Blue);
8636
8637const
8638 UNIT_CONST = 42;
8639
8640var
8641 UnitVar: string;
8642
8643implementation
8644
8645constructor TMyClass.Create;
8646begin
8647 FValue := 0;
8648end;
8649
8650procedure TMyClass.DoSomething;
8651begin
8652end;
8653
8654procedure StandaloneProc;
8655begin
8656end;
8657
8658end.
8659"#,
8660 )
8661 .unwrap();
8662
8663 let mut parser = FileParser::new();
8664 let symbols = parser.extract_symbols(&file).unwrap();
8665
8666 let get = |name: &str| {
8667 symbols
8668 .iter()
8669 .find(|symbol| symbol.name == name)
8670 .unwrap_or_else(|| panic!("missing {name}; got {symbols:?}"))
8671 };
8672
8673 assert_eq!(get("MyUnit").kind, SymbolKind::Class);
8674 assert_eq!(get("TMyClass").kind, SymbolKind::Class);
8675 assert_eq!(get("TMyRecord").kind, SymbolKind::Struct);
8676 assert_eq!(get("IMyInterface").kind, SymbolKind::Interface);
8677 assert_eq!(get("TMyEnum").kind, SymbolKind::Enum);
8678 assert_eq!(get("UNIT_CONST").kind, SymbolKind::Variable);
8679 assert_eq!(get("UnitVar").kind, SymbolKind::Variable);
8680 assert_eq!(get("StandaloneProc").kind, SymbolKind::Function);
8681
8682 let create_methods: Vec<&Symbol> = symbols.iter().filter(|s| s.name == "Create").collect();
8684 assert_eq!(create_methods.len(), 2); for m in create_methods {
8686 assert_eq!(m.kind, SymbolKind::Method);
8687 assert_eq!(m.scope_chain, vec!["TMyClass".to_string()]);
8688 }
8689
8690 let do_something_methods: Vec<&Symbol> =
8691 symbols.iter().filter(|s| s.name == "DoSomething").collect();
8692 assert_eq!(do_something_methods.len(), 2); for m in do_something_methods {
8694 assert_eq!(m.kind, SymbolKind::Method);
8695 assert_eq!(m.scope_chain, vec!["TMyClass".to_string()]);
8696 }
8697 }
8698}