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