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