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