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
5164pub(crate) fn json_document_value<'a>(root: &Node<'a>) -> Option<Node<'a>> {
5170 let mut cursor = root.walk();
5171 let value = root
5172 .named_children(&mut cursor)
5173 .find(|child| child.kind() != "comment");
5174 value
5175}
5176
5177fn extract_json_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
5178 let Some(value) = json_document_value(root) else {
5179 return Ok(Vec::new());
5180 };
5181
5182 if value.kind() != "object" {
5183 return Ok(Vec::new());
5184 }
5185
5186 let mut symbols = Vec::new();
5187 let mut cursor = value.walk();
5188 for child in value.named_children(&mut cursor) {
5189 if child.kind() != "pair" {
5190 continue;
5191 }
5192 let Some(key_node) = child.child_by_field_name("key") else {
5193 continue;
5194 };
5195 let name = node_text(source, &key_node).trim_matches('"').to_string();
5196 if name.is_empty() {
5197 continue;
5198 }
5199 symbols.push(Symbol {
5200 name,
5201 kind: SymbolKind::Variable,
5202 range: node_range_with_decorators(&child, source, LangId::Json),
5203 signature: None,
5204 scope_chain: vec![],
5205 exported: false,
5206 parent: None,
5207 });
5208 }
5209
5210 Ok(symbols)
5211}
5212
5213fn yaml_scalar_text(source: &str, node: &Node) -> String {
5215 node_text(source, node)
5216 .trim()
5217 .trim_matches('"')
5218 .trim_matches('\'')
5219 .to_string()
5220}
5221
5222fn yaml_find_mapping<'a>(node: &Node<'a>) -> Option<Node<'a>> {
5228 let mut current = *node;
5229 loop {
5230 match current.kind() {
5231 "block_mapping" | "flow_mapping" => return Some(current),
5232 "document" | "block_node" | "flow_node" => {
5233 let mut cursor = current.walk();
5234 let next = current
5235 .named_children(&mut cursor)
5236 .find(|child| !matches!(child.kind(), "tag" | "anchor"));
5237 match next {
5238 Some(inner) => current = inner,
5239 None => return None,
5240 }
5241 }
5242 _ => return None,
5243 }
5244 }
5245}
5246
5247fn yaml_get<'a>(mapping: &Node<'a>, source: &str, key: &str) -> Option<Node<'a>> {
5249 let mut cursor = mapping.walk();
5250 for pair in mapping.named_children(&mut cursor) {
5251 if pair.kind() != "block_mapping_pair" && pair.kind() != "flow_pair" {
5252 continue;
5253 }
5254 let Some(key_node) = pair.child_by_field_name("key") else {
5255 continue;
5256 };
5257 if yaml_scalar_text(source, &key_node) == key {
5258 return pair.child_by_field_name("value");
5259 }
5260 }
5261 None
5262}
5263
5264fn yaml_unwrap<'a>(node: &Node<'a>) -> Node<'a> {
5269 let mut current = *node;
5270 while current.kind() == "block_node" || current.kind() == "flow_node" {
5271 let mut cursor = current.walk();
5272 let next = current
5273 .named_children(&mut cursor)
5274 .find(|child| !matches!(child.kind(), "tag" | "anchor"));
5275 match next {
5276 Some(inner) => current = inner,
5277 None => break,
5278 }
5279 }
5280 current
5281}
5282
5283fn yaml_flatten_value(source: &str, node: &Node) -> String {
5284 let node = &yaml_unwrap(node);
5285 match node.kind() {
5286 "block_sequence" | "flow_sequence" => {
5287 let mut items = Vec::new();
5288 let mut cursor = node.walk();
5289 for child in node.named_children(&mut cursor) {
5290 let value = if child.kind() == "block_sequence_item" {
5292 child.named_child(0)
5293 } else {
5294 Some(child)
5295 };
5296 if let Some(v) = value {
5297 let v = yaml_unwrap(&v);
5298 if v.kind() != "block_mapping" && v.kind() != "flow_mapping" {
5300 let text = yaml_scalar_text(source, &v);
5301 if !text.is_empty() {
5302 items.push(text);
5303 }
5304 }
5305 }
5306 }
5307 items.join(",")
5308 }
5309 "block_mapping" | "flow_mapping" => String::new(),
5313 _ => yaml_scalar_text(source, node),
5314 }
5315}
5316
5317fn yaml_collect_values(
5321 source: &str,
5322 node: &Node,
5323 keys: &[&str],
5324 out: &mut Vec<String>,
5325 cap: usize,
5326) {
5327 if out.len() >= cap {
5328 return;
5329 }
5330 let mut cursor = node.walk();
5331 for child in node.named_children(&mut cursor) {
5332 if child.kind() == "block_mapping_pair" || child.kind() == "flow_pair" {
5333 if let Some(key_node) = child.child_by_field_name("key") {
5334 let key_text = yaml_scalar_text(source, &key_node);
5335 if keys.contains(&key_text.as_str()) {
5336 if let Some(value_node) = child.child_by_field_name("value") {
5337 let value_text = yaml_flatten_value(source, &value_node);
5338 if !value_text.is_empty() && out.len() < cap {
5339 out.push(format!("{}={}", key_text, value_text));
5340 }
5341 }
5342 }
5343 }
5344 }
5345 yaml_collect_values(source, &child, keys, out, cap);
5346 }
5347}
5348
5349fn yaml_collect_named_items(
5355 source: &str,
5356 node: &Node,
5357 parent_key: &str,
5358 label: &str,
5359 out: &mut Vec<String>,
5360 cap: usize,
5361) {
5362 let mut cursor = node.walk();
5363 for child in node.named_children(&mut cursor) {
5364 if (child.kind() == "block_mapping_pair" || child.kind() == "flow_pair")
5365 && child
5366 .child_by_field_name("key")
5367 .map(|k| yaml_scalar_text(source, &k))
5368 .as_deref()
5369 == Some(parent_key)
5370 {
5371 if let Some(seq) = child.child_by_field_name("value") {
5372 let seq = yaml_unwrap(&seq);
5373 let mut names = Vec::new();
5374 let mut seq_cursor = seq.walk();
5375 for item in seq.named_children(&mut seq_cursor) {
5376 let value = if item.kind() == "block_sequence_item" {
5377 item.named_child(0)
5378 } else {
5379 Some(item)
5380 };
5381 if let Some(v) = value {
5382 if let Some(map) = yaml_find_mapping(&v) {
5383 if let Some(name_node) = yaml_get(&map, source, "name") {
5384 let n = yaml_scalar_text(source, &name_node);
5385 if !n.is_empty() && names.len() < 16 {
5386 names.push(n);
5387 }
5388 }
5389 }
5390 }
5391 }
5392 if !names.is_empty() && out.len() < cap {
5393 out.push(format!("{}={}", label, names.join(",")));
5394 }
5395 }
5396 }
5397 yaml_collect_named_items(source, &child, parent_key, label, out, cap);
5398 }
5399}
5400
5401fn yaml_k8s_resource_symbol(source: &str, doc: &Node, mapping: &Node) -> Option<Symbol> {
5405 let api_version =
5406 yaml_get(mapping, source, "apiVersion").map(|n| yaml_scalar_text(source, &n))?;
5407 let kind = yaml_get(mapping, source, "kind").map(|n| yaml_scalar_text(source, &n))?;
5408 if api_version.is_empty() || kind.is_empty() {
5409 return None;
5410 }
5411
5412 let (name, namespace) = match yaml_get(mapping, source, "metadata") {
5413 Some(meta) => match yaml_find_mapping(&meta) {
5414 Some(meta_map) => {
5415 let name = yaml_get(&meta_map, source, "name")
5419 .map(|n| yaml_scalar_text(source, &n))
5420 .filter(|s| !s.is_empty())
5421 .or_else(|| {
5422 yaml_get(&meta_map, source, "generateName")
5423 .map(|n| yaml_scalar_text(source, &n))
5424 .filter(|s| !s.is_empty())
5425 });
5426 (
5427 name,
5428 yaml_get(&meta_map, source, "namespace").map(|n| yaml_scalar_text(source, &n)),
5429 )
5430 }
5431 None => (None, None),
5432 },
5433 None => (None, None),
5434 };
5435
5436 let res_name = name.clone().filter(|s| !s.is_empty());
5437 let sym_name = match (&namespace, &res_name) {
5438 (Some(ns), Some(n)) if !ns.is_empty() => format!("{}/{}/{}", ns, kind, n),
5439 (_, Some(n)) => format!("{}/{}", kind, n),
5440 _ => kind.clone(),
5441 };
5442
5443 let mut sig = format!("apiVersion={} kind={}", api_version, kind);
5444 if let Some(ns) = namespace.as_ref().filter(|s| !s.is_empty()) {
5445 sig.push_str(&format!(" namespace={}", ns));
5446 }
5447 if let Some(n) = res_name.as_ref() {
5448 sig.push_str(&format!(" name={}", n));
5449 }
5450 let mut extras = Vec::new();
5454 yaml_collect_values(
5455 source,
5456 mapping,
5457 &[
5458 "image",
5459 "containerPort",
5460 "port",
5461 "targetPort",
5462 "cpu",
5463 "memory",
5464 "storage",
5465 "verbs",
5466 "resources",
5467 "apiGroups",
5468 "mountPath",
5469 "replicas",
5470 "entrypoint",
5472 "command",
5473 "args",
5474 "schedule",
5475 ],
5476 &mut extras,
5477 24,
5478 );
5479 yaml_collect_named_items(source, mapping, "env", "env", &mut extras, 24);
5482 yaml_collect_named_items(source, mapping, "templates", "templates", &mut extras, 24);
5483 if !extras.is_empty() {
5484 sig.push(' ');
5485 sig.push_str(&extras.join(" "));
5486 }
5487
5488 Some(Symbol {
5489 name: sym_name,
5490 kind: SymbolKind::Class,
5491 range: node_range(doc),
5492 signature: Some(sig),
5493 scope_chain: vec![],
5494 exported: true,
5495 parent: None,
5496 })
5497}
5498
5499fn yaml_generic_keys(source: &str, mapping: &Node, symbols: &mut Vec<Symbol>) {
5502 let mut cursor = mapping.walk();
5503 for pair in mapping.named_children(&mut cursor) {
5504 if pair.kind() != "block_mapping_pair" && pair.kind() != "flow_pair" {
5505 continue;
5506 }
5507 let Some(key_node) = pair.child_by_field_name("key") else {
5508 continue;
5509 };
5510 let name = yaml_scalar_text(source, &key_node);
5511 if name.is_empty() {
5512 continue;
5513 }
5514 symbols.push(Symbol {
5515 name,
5516 kind: SymbolKind::Variable,
5517 range: node_range(&pair),
5518 signature: None,
5519 scope_chain: vec![],
5520 exported: false,
5521 parent: None,
5522 });
5523 }
5524}
5525
5526fn extract_yaml_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
5532 let mut symbols = Vec::new();
5533 let mut cursor = root.walk();
5534 for doc in root.named_children(&mut cursor) {
5535 if doc.kind() != "document" {
5536 continue;
5537 }
5538 let Some(mapping) = yaml_find_mapping(&doc) else {
5539 continue;
5540 };
5541 if let Some(symbol) = yaml_k8s_resource_symbol(source, &doc, &mapping) {
5542 symbols.push(symbol);
5543 } else {
5544 yaml_generic_keys(source, &mapping, &mut symbols);
5545 }
5546 }
5547 Ok(symbols)
5548}
5549
5550fn scala_scope_chain(node: &Node, source: &str) -> Vec<String> {
5551 let mut chain = Vec::new();
5552 let mut current = node.parent();
5553
5554 while let Some(parent) = current {
5555 match parent.kind() {
5556 "class_definition" | "object_definition" | "enum_definition" | "trait_definition" => {
5557 if let Some(name_node) = parent.child_by_field_name("name") {
5558 chain.push(node_text(source, &name_node).to_string());
5559 }
5560 }
5561 _ => {}
5562 }
5563 current = parent.parent();
5564 }
5565
5566 chain.reverse();
5567 chain
5568}
5569
5570fn extract_scala_symbols(
5571 source: &str,
5572 root: &Node,
5573 query: &Query,
5574) -> Result<Vec<Symbol>, AftError> {
5575 let lang = LangId::Scala;
5576 let capture_names = query.capture_names();
5577
5578 let mut symbols = Vec::new();
5579 let mut cursor = QueryCursor::new();
5580 let mut matches = cursor.matches(query, *root, source.as_bytes());
5581
5582 while let Some(m) = {
5583 matches.advance();
5584 matches.get()
5585 } {
5586 let mut class_name_node = None;
5587 let mut class_def_node = None;
5588 let mut object_name_node = None;
5589 let mut object_def_node = None;
5590 let mut enum_name_node = None;
5591 let mut enum_def_node = None;
5592 let mut trait_name_node = None;
5593 let mut trait_def_node = None;
5594 let mut fn_name_node = None;
5595 let mut fn_def_node = None;
5596 let mut val_name_node = None;
5597 let mut val_def_node = None;
5598 let mut var_name_node = None;
5599 let mut var_def_node = None;
5600 let mut type_name_node = None;
5601 let mut type_def_node = None;
5602
5603 for cap in m.captures {
5604 let Some(&name) = capture_names.get(cap.index as usize) else {
5605 continue;
5606 };
5607 match name {
5608 "class.name" => class_name_node = Some(cap.node),
5609 "class.def" => class_def_node = Some(cap.node),
5610 "object.name" => object_name_node = Some(cap.node),
5611 "object.def" => object_def_node = Some(cap.node),
5612 "enum.name" => enum_name_node = Some(cap.node),
5613 "enum.def" => enum_def_node = Some(cap.node),
5614 "trait.name" => trait_name_node = Some(cap.node),
5615 "trait.def" => trait_def_node = Some(cap.node),
5616 "fn.name" => fn_name_node = Some(cap.node),
5617 "fn.def" => fn_def_node = Some(cap.node),
5618 "val.name" => val_name_node = Some(cap.node),
5619 "val.def" => val_def_node = Some(cap.node),
5620 "var.name" => var_name_node = Some(cap.node),
5621 "var.def" => var_def_node = Some(cap.node),
5622 "given.name" => val_name_node = Some(cap.node),
5623 "given.def" => val_def_node = Some(cap.node),
5624 "type.name" => type_name_node = Some(cap.node),
5625 "type.def" => type_def_node = Some(cap.node),
5626 _ => {}
5627 }
5628 }
5629
5630 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
5631 symbols.push(Symbol {
5632 name: node_text(source, &name_node).to_string(),
5633 kind: SymbolKind::Class,
5634 range: node_range_with_decorators(&def_node, source, lang),
5635 signature: Some(extract_signature(source, &def_node)),
5636 scope_chain: scala_scope_chain(&def_node, source),
5637 exported: true,
5638 parent: scala_scope_chain(&def_node, source).last().cloned(),
5639 });
5640 }
5641
5642 if let (Some(name_node), Some(def_node)) = (object_name_node, object_def_node) {
5643 symbols.push(Symbol {
5644 name: node_text(source, &name_node).to_string(),
5645 kind: SymbolKind::Class,
5646 range: node_range_with_decorators(&def_node, source, lang),
5647 signature: Some(extract_signature(source, &def_node)),
5648 scope_chain: scala_scope_chain(&def_node, source),
5649 exported: true,
5650 parent: scala_scope_chain(&def_node, source).last().cloned(),
5651 });
5652 }
5653
5654 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
5655 symbols.push(Symbol {
5656 name: node_text(source, &name_node).to_string(),
5657 kind: SymbolKind::Enum,
5658 range: node_range_with_decorators(&def_node, source, lang),
5659 signature: Some(extract_signature(source, &def_node)),
5660 scope_chain: scala_scope_chain(&def_node, source),
5661 exported: true,
5662 parent: scala_scope_chain(&def_node, source).last().cloned(),
5663 });
5664 }
5665
5666 if let (Some(name_node), Some(def_node)) = (trait_name_node, trait_def_node) {
5667 symbols.push(Symbol {
5668 name: node_text(source, &name_node).to_string(),
5669 kind: SymbolKind::Interface,
5670 range: node_range_with_decorators(&def_node, source, lang),
5671 signature: Some(extract_signature(source, &def_node)),
5672 scope_chain: scala_scope_chain(&def_node, source),
5673 exported: true,
5674 parent: scala_scope_chain(&def_node, source).last().cloned(),
5675 });
5676 }
5677
5678 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
5679 let scope_chain = scala_scope_chain(&def_node, source);
5680 let kind = if scope_chain.is_empty() {
5681 SymbolKind::Function
5682 } else {
5683 SymbolKind::Method
5684 };
5685 symbols.push(Symbol {
5686 name: node_text(source, &name_node).to_string(),
5687 kind,
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)) = (val_name_node, val_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)) = (var_name_node, var_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::Variable,
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 if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
5723 let scope_chain = scala_scope_chain(&def_node, source);
5724 symbols.push(Symbol {
5725 name: node_text(source, &name_node).to_string(),
5726 kind: SymbolKind::TypeAlias,
5727 range: node_range_with_decorators(&def_node, source, lang),
5728 signature: Some(extract_signature(source, &def_node)),
5729 parent: scope_chain.last().cloned(),
5730 scope_chain,
5731 exported: true,
5732 });
5733 }
5734 }
5735
5736 dedup_symbols(&mut symbols);
5737 Ok(symbols)
5738}
5739
5740fn child_text_by_field_or_kind(
5741 node: &Node,
5742 source: &str,
5743 field_name: &str,
5744 kinds: &[&str],
5745) -> Option<String> {
5746 if let Some(name_node) = node.child_by_field_name(field_name) {
5747 return Some(node_text(source, &name_node).to_string());
5748 }
5749
5750 let mut cursor = node.walk();
5751 if !cursor.goto_first_child() {
5752 return None;
5753 }
5754
5755 loop {
5756 let child = cursor.node();
5757 if kinds.contains(&child.kind()) {
5758 return Some(node_text(source, &child).to_string());
5759 }
5760 if !cursor.goto_next_sibling() {
5761 break;
5762 }
5763 }
5764
5765 None
5766}
5767
5768fn push_captured_symbol(
5769 symbols: &mut Vec<Symbol>,
5770 source: &str,
5771 lang: LangId,
5772 name_node: Node,
5773 def_node: Node,
5774 kind: SymbolKind,
5775 scope_chain: Vec<String>,
5776 exported: bool,
5777) {
5778 symbols.push(Symbol {
5779 name: node_text(source, &name_node).to_string(),
5780 kind,
5781 range: node_range_with_decorators(&def_node, source, lang),
5782 signature: Some(extract_signature(source, &def_node)),
5783 parent: scope_chain.last().cloned(),
5784 scope_chain,
5785 exported,
5786 });
5787}
5788
5789fn range_spanning_nodes(start_node: &Node, end_node: &Node) -> Range {
5790 let start = start_node.start_position();
5791 let end = end_node.end_position();
5792 Range {
5793 start_line: start.row as u32,
5794 start_col: start.column as u32,
5795 end_line: end.row as u32,
5796 end_col: end.column as u32,
5797 }
5798}
5799
5800fn extract_signature_between(source: &str, start_node: &Node, end_node: &Node) -> String {
5801 let start = start_node.start_byte();
5802 let end = end_node.end_byte();
5803 let text = source.get(start..end).unwrap_or_default();
5804 let first_line = text.lines().next().unwrap_or(text);
5805 let trimmed = first_line.trim_end();
5806 let trimmed = trimmed.strip_suffix('{').unwrap_or(trimmed).trim_end();
5807 trimmed.to_string()
5808}
5809
5810fn groovy_scope_chain(node: &Node, source: &str) -> Vec<String> {
5811 let mut chain = Vec::new();
5812 let mut current = node.parent();
5813
5814 while let Some(parent) = current {
5815 if matches!(
5816 parent.kind(),
5817 "class_declaration"
5818 | "interface_declaration"
5819 | "trait_declaration"
5820 | "enum_declaration"
5821 | "record_declaration"
5822 | "annotation_type_declaration"
5823 ) {
5824 if let Some(name_node) = parent.child_by_field_name("name") {
5825 chain.push(node_text(source, &name_node).to_string());
5826 }
5827 }
5828 current = parent.parent();
5829 }
5830
5831 chain.reverse();
5832 chain
5833}
5834
5835fn groovy_keyword_task_symbol(
5839 source: &str,
5840 keyword_stmt: Node,
5841 declaration: Node,
5842) -> Option<Symbol> {
5843 if keyword_stmt.kind() != "expression_statement" || declaration.kind() != "command_chain" {
5844 return None;
5845 }
5846
5847 let keyword = keyword_stmt.named_child(0)?;
5848 if keyword.kind() != "identifier" || node_text(source, &keyword) != "task" {
5849 return None;
5850 }
5851
5852 let name_node = declaration.child_by_field_name("receiver")?;
5853 if name_node.kind() != "identifier" {
5854 return None;
5855 }
5856
5857 let body_node = declaration.child_by_field_name("argument")?;
5858 if body_node.kind() != "closure" {
5859 return None;
5860 }
5861
5862 Some(Symbol {
5863 name: node_text(source, &name_node).to_string(),
5864 kind: SymbolKind::Function,
5865 range: range_spanning_nodes(&keyword_stmt, &declaration),
5866 signature: Some(extract_signature_between(
5867 source,
5868 &keyword_stmt,
5869 &declaration,
5870 )),
5871 scope_chain: vec![],
5872 exported: true,
5873 parent: None,
5874 })
5875}
5876
5877fn groovy_registered_task_symbol(
5881 source: &str,
5882 invocation_stmt: Node,
5883 closure_stmt: Node,
5884) -> Option<Symbol> {
5885 if invocation_stmt.kind() != "expression_statement"
5886 || closure_stmt.kind() != "expression_statement"
5887 {
5888 return None;
5889 }
5890
5891 let invocation = invocation_stmt.named_child(0)?;
5892 if invocation.kind() != "method_invocation" {
5893 return None;
5894 }
5895
5896 let function = invocation.child_by_field_name("function")?;
5897 if function.kind() != "field_access" {
5898 return None;
5899 }
5900
5901 let object = function.child_by_field_name("object")?;
5902 let field = function.child_by_field_name("field")?;
5903 if node_text(source, &object) != "tasks" || node_text(source, &field) != "register" {
5904 return None;
5905 }
5906
5907 let arguments = invocation.child_by_field_name("arguments")?;
5908 let name_node = arguments.named_child(0)?;
5909 if name_node.kind() != "string_literal" {
5910 return None;
5911 }
5912
5913 let closure = closure_stmt.named_child(0)?;
5914 if closure.kind() != "closure" {
5915 return None;
5916 }
5917
5918 Some(Symbol {
5919 name: string_content(source, &name_node)?,
5920 kind: SymbolKind::Function,
5921 range: range_spanning_nodes(&invocation_stmt, &closure_stmt),
5922 signature: Some(extract_signature_between(
5923 source,
5924 &invocation_stmt,
5925 &closure_stmt,
5926 )),
5927 scope_chain: vec![],
5928 exported: true,
5929 parent: None,
5930 })
5931}
5932
5933fn collect_groovy_task_symbols(source: &str, root: &Node, symbols: &mut Vec<Symbol>) {
5934 let children: Vec<Node> = root.named_children(&mut root.walk()).collect();
5935 let mut index = 0;
5936 while index + 1 < children.len() {
5937 if let Some(symbol) =
5938 groovy_keyword_task_symbol(source, children[index], children[index + 1])
5939 {
5940 symbols.push(symbol);
5941 index += 2;
5942 continue;
5943 }
5944
5945 if let Some(symbol) =
5946 groovy_registered_task_symbol(source, children[index], children[index + 1])
5947 {
5948 symbols.push(symbol);
5949 index += 2;
5950 continue;
5951 }
5952
5953 index += 1;
5954 }
5955}
5956
5957fn java_scope_chain(node: &Node, source: &str) -> Vec<String> {
5958 let mut chain = Vec::new();
5959 let mut current = node.parent();
5960
5961 while let Some(parent) = current {
5962 if matches!(
5963 parent.kind(),
5964 "class_declaration"
5965 | "interface_declaration"
5966 | "annotation_type_declaration"
5967 | "enum_declaration"
5968 | "record_declaration"
5969 ) {
5970 if let Some(name_node) = parent.child_by_field_name("name") {
5971 chain.push(node_text(source, &name_node).to_string());
5972 }
5973 }
5974 current = parent.parent();
5975 }
5976
5977 chain.reverse();
5978 chain
5979}
5980
5981fn extract_java_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
5982 let lang = LangId::Java;
5983 let capture_names = query.capture_names();
5984 let mut symbols = Vec::new();
5985 let mut cursor = QueryCursor::new();
5986 let mut matches = cursor.matches(query, *root, source.as_bytes());
5987
5988 while let Some(m) = {
5989 matches.advance();
5990 matches.get()
5991 } {
5992 let mut class_name_node = None;
5993 let mut class_def_node = None;
5994 let mut interface_name_node = None;
5995 let mut interface_def_node = None;
5996 let mut enum_name_node = None;
5997 let mut enum_def_node = None;
5998 let mut struct_name_node = None;
5999 let mut struct_def_node = None;
6000 let mut fn_name_node = None;
6001 let mut fn_def_node = None;
6002 let mut var_name_node = None;
6003 let mut var_def_node = None;
6004
6005 for cap in m.captures {
6006 let Some(&name) = capture_names.get(cap.index as usize) else {
6007 continue;
6008 };
6009 match name {
6010 "class.name" => class_name_node = Some(cap.node),
6011 "class.def" => class_def_node = Some(cap.node),
6012 "interface.name" => interface_name_node = Some(cap.node),
6013 "interface.def" => interface_def_node = Some(cap.node),
6014 "enum.name" => enum_name_node = Some(cap.node),
6015 "enum.def" => enum_def_node = Some(cap.node),
6016 "struct.name" => struct_name_node = Some(cap.node),
6017 "struct.def" => struct_def_node = Some(cap.node),
6018 "fn.name" => fn_name_node = Some(cap.node),
6019 "fn.def" => fn_def_node = Some(cap.node),
6020 "var.name" => var_name_node = Some(cap.node),
6021 "var.def" => var_def_node = Some(cap.node),
6022 _ => {}
6023 }
6024 }
6025
6026 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
6027 push_captured_symbol(
6028 &mut symbols,
6029 source,
6030 lang,
6031 name_node,
6032 def_node,
6033 SymbolKind::Class,
6034 java_scope_chain(&def_node, source),
6035 true,
6036 );
6037 }
6038 if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
6039 push_captured_symbol(
6040 &mut symbols,
6041 source,
6042 lang,
6043 name_node,
6044 def_node,
6045 SymbolKind::Interface,
6046 java_scope_chain(&def_node, source),
6047 true,
6048 );
6049 }
6050 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
6051 push_captured_symbol(
6052 &mut symbols,
6053 source,
6054 lang,
6055 name_node,
6056 def_node,
6057 SymbolKind::Enum,
6058 java_scope_chain(&def_node, source),
6059 true,
6060 );
6061 }
6062 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
6063 push_captured_symbol(
6064 &mut symbols,
6065 source,
6066 lang,
6067 name_node,
6068 def_node,
6069 SymbolKind::Struct,
6070 java_scope_chain(&def_node, source),
6071 true,
6072 );
6073 }
6074 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
6075 let scope_chain = java_scope_chain(&def_node, source);
6076 let kind = if scope_chain.is_empty() {
6077 SymbolKind::Function
6078 } else {
6079 SymbolKind::Method
6080 };
6081 push_captured_symbol(
6082 &mut symbols,
6083 source,
6084 lang,
6085 name_node,
6086 def_node,
6087 kind,
6088 scope_chain,
6089 true,
6090 );
6091 }
6092 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
6093 push_captured_symbol(
6094 &mut symbols,
6095 source,
6096 lang,
6097 name_node,
6098 def_node,
6099 SymbolKind::Variable,
6100 java_scope_chain(&def_node, source),
6101 true,
6102 );
6103 }
6104 }
6105
6106 dedup_symbols(&mut symbols);
6107 Ok(symbols)
6108}
6109
6110fn ruby_scope_chain(node: &Node, source: &str) -> Vec<String> {
6111 let mut chain = Vec::new();
6112 let mut current = node.parent();
6113
6114 while let Some(parent) = current {
6115 if matches!(parent.kind(), "class" | "module") {
6116 if let Some(name_node) = parent.child_by_field_name("name") {
6117 chain.push(node_text(source, &name_node).to_string());
6118 }
6119 }
6120 current = parent.parent();
6121 }
6122
6123 chain.reverse();
6124 chain
6125}
6126
6127fn extract_ruby_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
6128 let lang = LangId::Ruby;
6129 let capture_names = query.capture_names();
6130 let mut symbols = Vec::new();
6131 let mut cursor = QueryCursor::new();
6132 let mut matches = cursor.matches(query, *root, source.as_bytes());
6133
6134 while let Some(m) = {
6135 matches.advance();
6136 matches.get()
6137 } {
6138 let mut module_name_node = None;
6139 let mut module_def_node = None;
6140 let mut class_name_node = None;
6141 let mut class_def_node = None;
6142 let mut fn_name_node = None;
6143 let mut fn_def_node = None;
6144 let mut var_name_node = None;
6145 let mut var_def_node = None;
6146
6147 for cap in m.captures {
6148 let Some(&name) = capture_names.get(cap.index as usize) else {
6149 continue;
6150 };
6151 match name {
6152 "module.name" => module_name_node = Some(cap.node),
6153 "module.def" => module_def_node = Some(cap.node),
6154 "class.name" => class_name_node = Some(cap.node),
6155 "class.def" => class_def_node = Some(cap.node),
6156 "fn.name" => fn_name_node = Some(cap.node),
6157 "fn.def" => fn_def_node = Some(cap.node),
6158 "var.name" => var_name_node = Some(cap.node),
6159 "var.def" => var_def_node = Some(cap.node),
6160 _ => {}
6161 }
6162 }
6163
6164 if let (Some(name_node), Some(def_node)) = (module_name_node, module_def_node) {
6165 push_captured_symbol(
6166 &mut symbols,
6167 source,
6168 lang,
6169 name_node,
6170 def_node,
6171 SymbolKind::Class,
6172 ruby_scope_chain(&def_node, source),
6173 true,
6174 );
6175 }
6176 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
6177 push_captured_symbol(
6178 &mut symbols,
6179 source,
6180 lang,
6181 name_node,
6182 def_node,
6183 SymbolKind::Class,
6184 ruby_scope_chain(&def_node, source),
6185 true,
6186 );
6187 }
6188 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
6189 let scope_chain = ruby_scope_chain(&def_node, source);
6190 let kind = if scope_chain.is_empty() {
6191 SymbolKind::Function
6192 } else {
6193 SymbolKind::Method
6194 };
6195 push_captured_symbol(
6196 &mut symbols,
6197 source,
6198 lang,
6199 name_node,
6200 def_node,
6201 kind,
6202 scope_chain,
6203 true,
6204 );
6205 }
6206 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
6207 push_captured_symbol(
6208 &mut symbols,
6209 source,
6210 lang,
6211 name_node,
6212 def_node,
6213 SymbolKind::Variable,
6214 ruby_scope_chain(&def_node, source),
6215 true,
6216 );
6217 }
6218 }
6219
6220 dedup_symbols(&mut symbols);
6221 Ok(symbols)
6222}
6223
6224fn kotlin_scope_chain(node: &Node, source: &str) -> Vec<String> {
6225 let mut chain = Vec::new();
6226 let mut current = node.parent();
6227
6228 while let Some(parent) = current {
6229 if matches!(parent.kind(), "class_declaration" | "object_declaration") {
6230 if let Some(name) =
6231 child_text_by_field_or_kind(&parent, source, "name", &["type_identifier"])
6232 {
6233 chain.push(name);
6234 }
6235 }
6236 current = parent.parent();
6237 }
6238
6239 chain.reverse();
6240 chain
6241}
6242
6243fn extract_kotlin_symbols(
6244 source: &str,
6245 root: &Node,
6246 query: &Query,
6247) -> Result<Vec<Symbol>, AftError> {
6248 let lang = LangId::Kotlin;
6249 let capture_names = query.capture_names();
6250 let mut symbols = Vec::new();
6251 let mut cursor = QueryCursor::new();
6252 let mut matches = cursor.matches(query, *root, source.as_bytes());
6253
6254 while let Some(m) = {
6255 matches.advance();
6256 matches.get()
6257 } {
6258 let mut class_name_node = None;
6259 let mut class_def_node = None;
6260 let mut object_name_node = None;
6261 let mut object_def_node = None;
6262 let mut fn_name_node = None;
6263 let mut fn_def_node = None;
6264 let mut var_name_node = None;
6265 let mut var_def_node = None;
6266 let mut type_name_node = None;
6267 let mut type_def_node = None;
6268
6269 for cap in m.captures {
6270 let Some(&name) = capture_names.get(cap.index as usize) else {
6271 continue;
6272 };
6273 match name {
6274 "class.name" => class_name_node = Some(cap.node),
6275 "class.def" => class_def_node = Some(cap.node),
6276 "object.name" => object_name_node = Some(cap.node),
6277 "object.def" => object_def_node = Some(cap.node),
6278 "fn.name" => fn_name_node = Some(cap.node),
6279 "fn.def" => fn_def_node = Some(cap.node),
6280 "var.name" => var_name_node = Some(cap.node),
6281 "var.def" => var_def_node = Some(cap.node),
6282 "type.name" => type_name_node = Some(cap.node),
6283 "type.def" => type_def_node = Some(cap.node),
6284 _ => {}
6285 }
6286 }
6287
6288 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
6289 push_captured_symbol(
6290 &mut symbols,
6291 source,
6292 lang,
6293 name_node,
6294 def_node,
6295 SymbolKind::Class,
6296 kotlin_scope_chain(&def_node, source),
6297 true,
6298 );
6299 }
6300 if let (Some(name_node), Some(def_node)) = (object_name_node, object_def_node) {
6301 push_captured_symbol(
6302 &mut symbols,
6303 source,
6304 lang,
6305 name_node,
6306 def_node,
6307 SymbolKind::Class,
6308 kotlin_scope_chain(&def_node, source),
6309 true,
6310 );
6311 }
6312 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
6313 let scope_chain = kotlin_scope_chain(&def_node, source);
6314 let kind = if scope_chain.is_empty() {
6315 SymbolKind::Function
6316 } else {
6317 SymbolKind::Method
6318 };
6319 push_captured_symbol(
6320 &mut symbols,
6321 source,
6322 lang,
6323 name_node,
6324 def_node,
6325 kind,
6326 scope_chain,
6327 true,
6328 );
6329 }
6330 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
6331 push_captured_symbol(
6332 &mut symbols,
6333 source,
6334 lang,
6335 name_node,
6336 def_node,
6337 SymbolKind::Variable,
6338 kotlin_scope_chain(&def_node, source),
6339 true,
6340 );
6341 }
6342 if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
6343 push_captured_symbol(
6344 &mut symbols,
6345 source,
6346 lang,
6347 name_node,
6348 def_node,
6349 SymbolKind::TypeAlias,
6350 kotlin_scope_chain(&def_node, source),
6351 true,
6352 );
6353 }
6354 }
6355
6356 dedup_symbols(&mut symbols);
6357 Ok(symbols)
6358}
6359
6360fn swift_scope_chain(node: &Node, source: &str) -> Vec<String> {
6361 let mut chain = Vec::new();
6362 let mut current = node.parent();
6363
6364 while let Some(parent) = current {
6365 if matches!(parent.kind(), "class_declaration" | "protocol_declaration") {
6366 if let Some(name_node) = parent.child_by_field_name("name") {
6367 chain.push(node_text(source, &name_node).to_string());
6368 }
6369 }
6370 current = parent.parent();
6371 }
6372
6373 chain.reverse();
6374 chain
6375}
6376
6377fn swift_type_kind(source: &str, node: &Node) -> SymbolKind {
6378 match node
6379 .child_by_field_name("declaration_kind")
6380 .map(|kind_node| node_text(source, &kind_node))
6381 {
6382 Some("struct") => SymbolKind::Struct,
6383 Some("enum") => SymbolKind::Enum,
6384 _ => SymbolKind::Class,
6385 }
6386}
6387
6388fn extract_swift_symbols(
6389 source: &str,
6390 root: &Node,
6391 query: &Query,
6392) -> Result<Vec<Symbol>, AftError> {
6393 let lang = LangId::Swift;
6394 let capture_names = query.capture_names();
6395 let mut symbols = Vec::new();
6396 let mut cursor = QueryCursor::new();
6397 let mut matches = cursor.matches(query, *root, source.as_bytes());
6398
6399 while let Some(m) = {
6400 matches.advance();
6401 matches.get()
6402 } {
6403 let mut class_name_node = None;
6404 let mut class_def_node = None;
6405 let mut interface_name_node = None;
6406 let mut interface_def_node = None;
6407 let mut fn_name_node = None;
6408 let mut fn_def_node = None;
6409 let mut var_name_node = None;
6410 let mut var_def_node = None;
6411 let mut type_name_node = None;
6412 let mut type_def_node = None;
6413
6414 for cap in m.captures {
6415 let Some(&name) = capture_names.get(cap.index as usize) else {
6416 continue;
6417 };
6418 match name {
6419 "class.name" => class_name_node = Some(cap.node),
6420 "class.def" => class_def_node = Some(cap.node),
6421 "interface.name" => interface_name_node = Some(cap.node),
6422 "interface.def" => interface_def_node = Some(cap.node),
6423 "fn.name" => fn_name_node = Some(cap.node),
6424 "fn.def" => fn_def_node = Some(cap.node),
6425 "var.name" => var_name_node = Some(cap.node),
6426 "var.def" => var_def_node = Some(cap.node),
6427 "type.name" => type_name_node = Some(cap.node),
6428 "type.def" => type_def_node = Some(cap.node),
6429 _ => {}
6430 }
6431 }
6432
6433 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
6434 let kind = swift_type_kind(source, &def_node);
6435 push_captured_symbol(
6436 &mut symbols,
6437 source,
6438 lang,
6439 name_node,
6440 def_node,
6441 kind,
6442 swift_scope_chain(&def_node, source),
6443 true,
6444 );
6445 }
6446 if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
6447 push_captured_symbol(
6448 &mut symbols,
6449 source,
6450 lang,
6451 name_node,
6452 def_node,
6453 SymbolKind::Interface,
6454 swift_scope_chain(&def_node, source),
6455 true,
6456 );
6457 }
6458 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
6459 let scope_chain = swift_scope_chain(&def_node, source);
6460 let kind = if scope_chain.is_empty() {
6461 SymbolKind::Function
6462 } else {
6463 SymbolKind::Method
6464 };
6465 push_captured_symbol(
6466 &mut symbols,
6467 source,
6468 lang,
6469 name_node,
6470 def_node,
6471 kind,
6472 scope_chain,
6473 true,
6474 );
6475 }
6476 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
6477 push_captured_symbol(
6478 &mut symbols,
6479 source,
6480 lang,
6481 name_node,
6482 def_node,
6483 SymbolKind::Variable,
6484 swift_scope_chain(&def_node, source),
6485 true,
6486 );
6487 }
6488 if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
6489 push_captured_symbol(
6490 &mut symbols,
6491 source,
6492 lang,
6493 name_node,
6494 def_node,
6495 SymbolKind::TypeAlias,
6496 swift_scope_chain(&def_node, source),
6497 true,
6498 );
6499 }
6500 }
6501
6502 dedup_symbols(&mut symbols);
6503 Ok(symbols)
6504}
6505
6506fn php_scope_chain(node: &Node, source: &str) -> Vec<String> {
6507 let mut chain = Vec::new();
6508 let mut current = node.parent();
6509
6510 while let Some(parent) = current {
6511 match parent.kind() {
6512 "namespace_definition"
6513 | "class_declaration"
6514 | "interface_declaration"
6515 | "trait_declaration"
6516 | "enum_declaration" => {
6517 if let Some(name_node) = parent.child_by_field_name("name") {
6518 chain.push(node_text(source, &name_node).to_string());
6519 }
6520 }
6521 _ => {}
6522 }
6523 current = parent.parent();
6524 }
6525
6526 chain.reverse();
6527 chain
6528}
6529
6530fn extract_scss_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
6531 let lang = LangId::Scss;
6532 let capture_names = query.capture_names();
6533 let mut symbols = Vec::new();
6534 let mut cursor = QueryCursor::new();
6535 let mut matches = cursor.matches(query, *root, source.as_bytes());
6536
6537 while let Some(m) = {
6538 matches.advance();
6539 matches.get()
6540 } {
6541 let mut mixin_name_node = None;
6542 let mut mixin_def_node = None;
6543 let mut fn_name_node = None;
6544 let mut fn_def_node = None;
6545 let mut var_name_node = None;
6546 let mut var_def_node = None;
6547 let mut selector_name_node = None;
6548 let mut selector_def_node = None;
6549
6550 for cap in m.captures {
6551 let Some(&name) = capture_names.get(cap.index as usize) else {
6552 continue;
6553 };
6554 match name {
6555 "mixin.name" => mixin_name_node = Some(cap.node),
6556 "mixin.def" => mixin_def_node = Some(cap.node),
6557 "fn.name" => fn_name_node = Some(cap.node),
6558 "fn.def" => fn_def_node = Some(cap.node),
6559 "var.name" => var_name_node = Some(cap.node),
6560 "var.def" => var_def_node = Some(cap.node),
6561 "selector.name" => selector_name_node = Some(cap.node),
6562 "selector.def" => selector_def_node = Some(cap.node),
6563 _ => {}
6564 }
6565 }
6566
6567 if let (Some(name_node), Some(def_node)) = (mixin_name_node, mixin_def_node) {
6568 push_captured_symbol(
6569 &mut symbols,
6570 source,
6571 lang,
6572 name_node,
6573 def_node,
6574 SymbolKind::Function,
6575 vec![],
6576 true,
6577 );
6578 }
6579 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
6580 push_captured_symbol(
6581 &mut symbols,
6582 source,
6583 lang,
6584 name_node,
6585 def_node,
6586 SymbolKind::Function,
6587 vec![],
6588 true,
6589 );
6590 }
6591 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
6592 if !node_text(source, &name_node).starts_with('$') {
6593 continue;
6594 }
6595 push_captured_symbol(
6596 &mut symbols,
6597 source,
6598 lang,
6599 name_node,
6600 def_node,
6601 SymbolKind::Variable,
6602 vec![],
6603 true,
6604 );
6605 }
6606 if let (Some(name_node), Some(def_node)) = (selector_name_node, selector_def_node) {
6607 push_captured_symbol(
6608 &mut symbols,
6609 source,
6610 lang,
6611 name_node,
6612 def_node,
6613 SymbolKind::Class,
6614 vec![],
6615 true,
6616 );
6617 }
6618 }
6619
6620 Ok(symbols)
6621}
6622
6623fn extract_php_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
6624 let lang = LangId::Php;
6625 let capture_names = query.capture_names();
6626 let mut symbols = Vec::new();
6627 let mut cursor = QueryCursor::new();
6628 let mut matches = cursor.matches(query, *root, source.as_bytes());
6629
6630 while let Some(m) = {
6631 matches.advance();
6632 matches.get()
6633 } {
6634 let mut namespace_name_node = None;
6635 let mut namespace_def_node = None;
6636 let mut class_name_node = None;
6637 let mut class_def_node = None;
6638 let mut interface_name_node = None;
6639 let mut interface_def_node = None;
6640 let mut trait_name_node = None;
6641 let mut trait_def_node = None;
6642 let mut enum_name_node = None;
6643 let mut enum_def_node = None;
6644 let mut fn_name_node = None;
6645 let mut fn_def_node = None;
6646 let mut var_name_node = None;
6647 let mut var_def_node = None;
6648
6649 for cap in m.captures {
6650 let Some(&name) = capture_names.get(cap.index as usize) else {
6651 continue;
6652 };
6653 match name {
6654 "namespace.name" => namespace_name_node = Some(cap.node),
6655 "namespace.def" => namespace_def_node = Some(cap.node),
6656 "class.name" => class_name_node = Some(cap.node),
6657 "class.def" => class_def_node = Some(cap.node),
6658 "interface.name" => interface_name_node = Some(cap.node),
6659 "interface.def" => interface_def_node = Some(cap.node),
6660 "trait.name" => trait_name_node = Some(cap.node),
6661 "trait.def" => trait_def_node = Some(cap.node),
6662 "enum.name" => enum_name_node = Some(cap.node),
6663 "enum.def" => enum_def_node = Some(cap.node),
6664 "fn.name" => fn_name_node = Some(cap.node),
6665 "fn.def" => fn_def_node = Some(cap.node),
6666 "var.name" => var_name_node = Some(cap.node),
6667 "var.def" => var_def_node = Some(cap.node),
6668 _ => {}
6669 }
6670 }
6671
6672 if let (Some(name_node), Some(def_node)) = (namespace_name_node, namespace_def_node) {
6673 push_captured_symbol(
6674 &mut symbols,
6675 source,
6676 lang,
6677 name_node,
6678 def_node,
6679 SymbolKind::Class,
6680 php_scope_chain(&def_node, source),
6681 true,
6682 );
6683 }
6684 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
6685 push_captured_symbol(
6686 &mut symbols,
6687 source,
6688 lang,
6689 name_node,
6690 def_node,
6691 SymbolKind::Class,
6692 php_scope_chain(&def_node, source),
6693 true,
6694 );
6695 }
6696 if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
6697 push_captured_symbol(
6698 &mut symbols,
6699 source,
6700 lang,
6701 name_node,
6702 def_node,
6703 SymbolKind::Interface,
6704 php_scope_chain(&def_node, source),
6705 true,
6706 );
6707 }
6708 if let (Some(name_node), Some(def_node)) = (trait_name_node, trait_def_node) {
6709 push_captured_symbol(
6710 &mut symbols,
6711 source,
6712 lang,
6713 name_node,
6714 def_node,
6715 SymbolKind::Interface,
6716 php_scope_chain(&def_node, source),
6717 true,
6718 );
6719 }
6720 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
6721 push_captured_symbol(
6722 &mut symbols,
6723 source,
6724 lang,
6725 name_node,
6726 def_node,
6727 SymbolKind::Enum,
6728 php_scope_chain(&def_node, source),
6729 true,
6730 );
6731 }
6732 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
6733 let scope_chain = php_scope_chain(&def_node, source);
6734 let kind = if scope_chain.is_empty() || def_node.kind() == "function_definition" {
6735 SymbolKind::Function
6736 } else {
6737 SymbolKind::Method
6738 };
6739 push_captured_symbol(
6740 &mut symbols,
6741 source,
6742 lang,
6743 name_node,
6744 def_node,
6745 kind,
6746 scope_chain,
6747 true,
6748 );
6749 }
6750 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
6751 push_captured_symbol(
6752 &mut symbols,
6753 source,
6754 lang,
6755 name_node,
6756 def_node,
6757 SymbolKind::Variable,
6758 php_scope_chain(&def_node, source),
6759 true,
6760 );
6761 }
6762 }
6763
6764 dedup_symbols(&mut symbols);
6765 Ok(symbols)
6766}
6767
6768fn lua_scope_chain(node: &Node, source: &str) -> Vec<String> {
6769 let mut chain = Vec::new();
6770
6771 if node.kind() == "function_declaration" {
6772 if let Some(name_node) = node.child_by_field_name("name") {
6773 match name_node.kind() {
6774 "dot_index_expression" | "method_index_expression" => {
6775 if let Some(table_node) = name_node.child_by_field_name("table") {
6776 chain.push(node_text(source, &table_node).to_string());
6777 }
6778 }
6779 _ => {}
6780 }
6781 }
6782 }
6783
6784 chain
6785}
6786
6787fn extract_lua_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
6788 let lang = LangId::Lua;
6789 let capture_names = query.capture_names();
6790 let mut symbols = Vec::new();
6791 let mut cursor = QueryCursor::new();
6792 let mut matches = cursor.matches(query, *root, source.as_bytes());
6793
6794 while let Some(m) = {
6795 matches.advance();
6796 matches.get()
6797 } {
6798 let mut fn_name_node = None;
6799 let mut fn_def_node = None;
6800 let mut var_name_node = None;
6801 let mut var_def_node = None;
6802
6803 for cap in m.captures {
6804 let Some(&name) = capture_names.get(cap.index as usize) else {
6805 continue;
6806 };
6807 match name {
6808 "fn.name" => fn_name_node = Some(cap.node),
6809 "fn.def" => fn_def_node = Some(cap.node),
6810 "var.name" => var_name_node = Some(cap.node),
6811 "var.def" => var_def_node = Some(cap.node),
6812 _ => {}
6813 }
6814 }
6815
6816 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
6817 let scope_chain = lua_scope_chain(&def_node, source);
6818 let kind = if scope_chain.is_empty() {
6819 SymbolKind::Function
6820 } else {
6821 SymbolKind::Method
6822 };
6823 push_captured_symbol(
6824 &mut symbols,
6825 source,
6826 lang,
6827 name_node,
6828 def_node,
6829 kind,
6830 scope_chain,
6831 true,
6832 );
6833 }
6834 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
6835 push_captured_symbol(
6836 &mut symbols,
6837 source,
6838 lang,
6839 name_node,
6840 def_node,
6841 SymbolKind::Variable,
6842 vec![],
6843 true,
6844 );
6845 }
6846 }
6847
6848 dedup_symbols(&mut symbols);
6849 Ok(symbols)
6850}
6851
6852fn perl_package_name(source: &str, node: &Node) -> Option<String> {
6853 let mut cursor = node.walk();
6854 if !cursor.goto_first_child() {
6855 return None;
6856 }
6857
6858 loop {
6859 let child = cursor.node();
6860 if child.kind() == "package" {
6861 return Some(node_text(source, &child).to_string());
6862 }
6863 if !cursor.goto_next_sibling() {
6864 break;
6865 }
6866 }
6867
6868 None
6869}
6870
6871fn perl_scope_chain(node: &Node, source: &str) -> Vec<String> {
6872 let mut chain = Vec::new();
6873 let mut current = node.parent();
6874
6875 while let Some(parent) = current {
6876 if parent.kind() == "package_statement" {
6877 if let Some(name) = perl_package_name(source, &parent) {
6878 chain.push(name);
6879 }
6880 }
6881 current = parent.parent();
6882 }
6883
6884 if chain.is_empty() && node.kind() != "package_statement" {
6885 let mut sibling = node.prev_sibling();
6886 while let Some(prev) = sibling {
6887 if prev.kind() == "package_statement" {
6888 if let Some(name) = perl_package_name(source, &prev) {
6889 chain.push(name);
6890 }
6891 break;
6892 }
6893 sibling = prev.prev_sibling();
6894 }
6895 }
6896
6897 chain.reverse();
6898 chain
6899}
6900
6901fn extract_perl_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
6902 let lang = LangId::Perl;
6903 let capture_names = query.capture_names();
6904 let mut symbols = Vec::new();
6905 let mut cursor = QueryCursor::new();
6906 let mut matches = cursor.matches(query, *root, source.as_bytes());
6907
6908 while let Some(m) = {
6909 matches.advance();
6910 matches.get()
6911 } {
6912 let mut package_name_node = None;
6913 let mut package_def_node = None;
6914 let mut fn_name_node = None;
6915 let mut fn_def_node = None;
6916 let mut var_name_node = None;
6917 let mut var_def_node = None;
6918 let mut const_pragma_node = None;
6919 let mut const_name_node = None;
6920 let mut const_def_node = None;
6921
6922 for cap in m.captures {
6923 let Some(&name) = capture_names.get(cap.index as usize) else {
6924 continue;
6925 };
6926 match name {
6927 "package.name" => package_name_node = Some(cap.node),
6928 "package.def" => package_def_node = Some(cap.node),
6929 "fn.name" => fn_name_node = Some(cap.node),
6930 "fn.def" => fn_def_node = Some(cap.node),
6931 "var.name" => var_name_node = Some(cap.node),
6932 "var.def" => var_def_node = Some(cap.node),
6933 "const.pragma" => const_pragma_node = Some(cap.node),
6934 "const.name" => const_name_node = Some(cap.node),
6935 "const.def" => const_def_node = Some(cap.node),
6936 _ => {}
6937 }
6938 }
6939
6940 if let (Some(pragma_node), Some(name_node), Some(def_node)) =
6945 (const_pragma_node, const_name_node, const_def_node)
6946 {
6947 if node_text(source, &pragma_node) == "constant" {
6948 var_name_node = Some(name_node);
6949 var_def_node = Some(def_node);
6950 }
6951 }
6952
6953 if let (Some(name_node), Some(def_node)) = (package_name_node, package_def_node) {
6954 push_captured_symbol(
6955 &mut symbols,
6956 source,
6957 lang,
6958 name_node,
6959 def_node,
6960 SymbolKind::Class,
6961 vec![],
6962 true,
6963 );
6964 }
6965 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
6966 let scope_chain = perl_scope_chain(&def_node, source);
6967 let kind = if scope_chain.is_empty() {
6968 SymbolKind::Function
6969 } else {
6970 SymbolKind::Method
6971 };
6972 push_captured_symbol(
6973 &mut symbols,
6974 source,
6975 lang,
6976 name_node,
6977 def_node,
6978 kind,
6979 scope_chain,
6980 true,
6981 );
6982 }
6983 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
6984 push_captured_symbol(
6985 &mut symbols,
6986 source,
6987 lang,
6988 name_node,
6989 def_node,
6990 SymbolKind::Variable,
6991 perl_scope_chain(&def_node, source),
6992 true,
6993 );
6994 }
6995 }
6996
6997 dedup_symbols(&mut symbols);
6998 Ok(symbols)
6999}
7000
7001fn extract_vue_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
7002 let mut symbols = Vec::new();
7003 collect_vue_sections(source, root, &mut symbols, true);
7004 dedup_symbols(&mut symbols);
7005 Ok(symbols)
7006}
7007
7008fn collect_vue_sections(
7009 source: &str,
7010 node: &Node,
7011 symbols: &mut Vec<Symbol>,
7012 allow_sections: bool,
7013) {
7014 let mut cursor = node.walk();
7015 if !cursor.goto_first_child() {
7016 return;
7017 }
7018
7019 loop {
7020 let child = cursor.node();
7021 if let Some(section_name) = vue_section_name(&child) {
7022 if allow_sections {
7023 symbols.push(Symbol {
7024 name: section_name.to_string(),
7025 kind: SymbolKind::Heading,
7026 range: node_range(&child),
7027 signature: vue_opening_tag_signature(source, &child),
7028 scope_chain: vec![],
7029 exported: false,
7030 parent: None,
7031 });
7032 }
7033 } else {
7034 collect_vue_sections(
7035 source,
7036 &child,
7037 symbols,
7038 allow_sections && child.kind() == "document",
7039 );
7040 }
7041
7042 if !cursor.goto_next_sibling() {
7043 break;
7044 }
7045 }
7046}
7047
7048fn vue_section_name(node: &Node) -> Option<&'static str> {
7049 match node.kind() {
7050 "template_element" => Some("template"),
7051 "script_element" => Some("script"),
7052 "style_element" => Some("style"),
7053 _ => None,
7054 }
7055}
7056
7057fn vue_opening_tag_signature(source: &str, node: &Node) -> Option<String> {
7058 find_child_by_kind(*node, "start_tag")
7059 .or_else(|| find_child_by_kind(*node, "script_start_tag"))
7060 .or_else(|| find_child_by_kind(*node, "style_start_tag"))
7061 .or_else(|| find_child_by_kind(*node, "template_start_tag"))
7062 .map(|tag| node_text(source, &tag).trim().to_string())
7063}
7064
7065fn source_line_end_col(source: &str, line: u32) -> u32 {
7066 source
7067 .lines()
7068 .nth(line as usize)
7069 .map(|line| line.len() as u32)
7070 .unwrap_or(0)
7071}
7072
7073fn extract_html_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
7074 let mut headings = Vec::new();
7075 collect_html_headings(source, root, &mut headings);
7076
7077 let total_lines = source.lines().count() as u32;
7078
7079 for i in 0..headings.len() {
7083 let level = headings[i].level;
7084 let section_end = headings[i + 1..]
7085 .iter()
7086 .find(|heading| heading.level <= level)
7087 .map(|heading| heading.symbol.range.start_line.saturating_sub(1))
7088 .unwrap_or_else(|| total_lines.saturating_sub(1));
7089 headings[i].symbol.range.end_line = section_end;
7090 if section_end != headings[i].symbol.range.start_line {
7091 headings[i].symbol.range.end_col = source_line_end_col(source, section_end);
7092 }
7093 }
7094
7095 let mut scope_stack: Vec<(u8, String)> = Vec::new(); for heading in &mut headings {
7098 while scope_stack
7100 .last()
7101 .is_some_and(|(level, _)| *level >= heading.level)
7102 {
7103 scope_stack.pop();
7104 }
7105 heading.symbol.scope_chain = scope_stack.iter().map(|(_, name)| name.clone()).collect();
7106 heading.symbol.parent = scope_stack.last().map(|(_, name)| name.clone());
7107 scope_stack.push((heading.level, heading.symbol.name.clone()));
7108 }
7109
7110 Ok(headings.into_iter().map(|heading| heading.symbol).collect())
7111}
7112
7113struct HtmlHeading {
7115 level: u8,
7116 symbol: Symbol,
7117 anchor_id: Option<String>,
7118}
7119
7120fn collect_html_headings(source: &str, node: &Node, headings: &mut Vec<HtmlHeading>) {
7122 let mut cursor = node.walk();
7123 if !cursor.goto_first_child() {
7124 return;
7125 }
7126
7127 loop {
7128 let child = cursor.node();
7129 if child.kind() == "element" {
7130 if let Some(start_tag) = child
7132 .child_by_field_name("start_tag")
7133 .or_else(|| child.child(0).filter(|c| c.kind() == "start_tag"))
7134 {
7135 if let Some(tag_name_node) = start_tag
7136 .child_by_field_name("tag_name")
7137 .or_else(|| start_tag.child(1).filter(|c| c.kind() == "tag_name"))
7138 {
7139 let tag_name = node_text(source, &tag_name_node).to_lowercase();
7140 if let Some(level) = match tag_name.as_str() {
7141 "h1" => Some(1u8),
7142 "h2" => Some(2),
7143 "h3" => Some(3),
7144 "h4" => Some(4),
7145 "h5" => Some(5),
7146 "h6" => Some(6),
7147 _ => None,
7148 } {
7149 let text = extract_element_text(source, &child).trim().to_string();
7151 if !text.is_empty() {
7152 let range = node_range(&child);
7153 let signature = format!("<h{}> {}", level, text);
7154 headings.push(HtmlHeading {
7155 level,
7156 anchor_id: html_heading_anchor_id(source, &start_tag),
7157 symbol: Symbol {
7158 name: text,
7159 kind: SymbolKind::Heading,
7160 range,
7161 signature: Some(signature),
7162 scope_chain: vec![], exported: false,
7164 parent: None, },
7166 });
7167 }
7168 }
7169 }
7170 }
7171 collect_html_headings(source, &child, headings);
7173 } else {
7174 collect_html_headings(source, &child, headings);
7176 }
7177
7178 if !cursor.goto_next_sibling() {
7179 break;
7180 }
7181 }
7182}
7183
7184fn extract_html_heading_anchors(source: &str, root: &Node) -> Vec<crate::language::HeadingAnchor> {
7185 let mut headings = Vec::new();
7186 collect_html_headings(source, root, &mut headings);
7187 headings
7188 .into_iter()
7189 .filter_map(|heading| {
7190 heading.anchor_id.map(|id| crate::language::HeadingAnchor {
7191 start_line: heading.symbol.range.start_line,
7192 start_col: heading.symbol.range.start_col,
7193 id,
7194 })
7195 })
7196 .collect()
7197}
7198
7199fn html_heading_anchor_id(source: &str, start_tag: &Node) -> Option<String> {
7200 for expected_name in ["id", "name"] {
7201 let mut cursor = start_tag.walk();
7202 for attribute in start_tag.named_children(&mut cursor) {
7203 if attribute.kind() != "attribute" {
7204 continue;
7205 }
7206 let Some(name_node) = attribute
7207 .child_by_field_name("name")
7208 .or_else(|| find_child_by_kind(attribute, "attribute_name"))
7209 else {
7210 continue;
7211 };
7212 if !node_text(source, &name_node).eq_ignore_ascii_case(expected_name) {
7213 continue;
7214 }
7215 let Some(value_node) = attribute
7216 .child_by_field_name("value")
7217 .or_else(|| find_child_by_kind(attribute, "quoted_attribute_value"))
7218 .or_else(|| find_child_by_kind(attribute, "attribute_value"))
7219 else {
7220 continue;
7221 };
7222 let raw_value = node_text(source, &value_node).trim();
7223 let value = raw_value
7224 .strip_prefix('"')
7225 .and_then(|value| value.strip_suffix('"'))
7226 .or_else(|| {
7227 raw_value
7228 .strip_prefix('\'')
7229 .and_then(|value| value.strip_suffix('\''))
7230 })
7231 .unwrap_or(raw_value)
7232 .trim();
7233 if !value.is_empty() {
7234 return Some(value.to_string());
7235 }
7236 }
7237 }
7238 None
7239}
7240
7241fn extract_element_text(source: &str, node: &Node) -> String {
7243 let mut text = String::new();
7244 let mut cursor = node.walk();
7245 if !cursor.goto_first_child() {
7246 return text;
7247 }
7248 loop {
7249 let child = cursor.node();
7250 match child.kind() {
7251 "text" => {
7252 text.push_str(node_text(source, &child));
7253 }
7254 "element" => {
7255 text.push_str(&extract_element_text(source, &child));
7257 }
7258 _ => {}
7259 }
7260 if !cursor.goto_next_sibling() {
7261 break;
7262 }
7263 }
7264 text
7265}
7266
7267struct RawHeading {
7268 name: String,
7269 level: u8,
7270 range: Range,
7271}
7272
7273fn parse_atx_heading(source: &str, node: &Node) -> Option<(String, u8)> {
7274 let mut heading_level = 1;
7275 let mut heading_name = String::new();
7276 let mut cursor = node.walk();
7277 if cursor.goto_first_child() {
7278 loop {
7279 let child = cursor.node();
7280 let kind = child.kind();
7281 if kind.starts_with("atx_h") && kind.ends_with("_marker") {
7282 heading_level = kind
7283 .strip_prefix("atx_h")
7284 .and_then(|s| s.strip_suffix("_marker"))
7285 .and_then(|s| s.parse::<u8>().ok())
7286 .unwrap_or(1);
7287 } else if kind == "inline" {
7288 heading_name = node_text(source, &child).trim().to_string();
7289 }
7290 if !cursor.goto_next_sibling() {
7291 break;
7292 }
7293 }
7294 }
7295 if heading_name.is_empty() {
7296 None
7297 } else {
7298 Some((heading_name, heading_level))
7299 }
7300}
7301
7302fn parse_setext_heading(source: &str, node: &Node) -> Option<(String, u8)> {
7303 let mut heading_level = 1;
7304 let mut heading_name = String::new();
7305 let mut cursor = node.walk();
7306 if cursor.goto_first_child() {
7307 loop {
7308 let child = cursor.node();
7309 let kind = child.kind();
7310 if kind.starts_with("setext_h") && kind.ends_with("_underline") {
7311 heading_level = kind
7312 .strip_prefix("setext_h")
7313 .and_then(|s| s.strip_suffix("_underline"))
7314 .and_then(|s| s.parse::<u8>().ok())
7315 .unwrap_or(1);
7316 } else if kind == "paragraph" {
7317 heading_name = node_text(source, &child).trim().to_string();
7318 }
7319 if !cursor.goto_next_sibling() {
7320 break;
7321 }
7322 }
7323 }
7324 if heading_name.is_empty() {
7325 None
7326 } else {
7327 Some((heading_name, heading_level))
7328 }
7329}
7330
7331fn collect_headings(source: &str, node: &Node, headings: &mut Vec<RawHeading>) {
7332 let kind = node.kind();
7333 if kind == "atx_heading" {
7334 if let Some((name, level)) = parse_atx_heading(source, node) {
7335 headings.push(RawHeading {
7336 name,
7337 level,
7338 range: node_range(node),
7339 });
7340 }
7341 } else if kind == "setext_heading" {
7342 if let Some((name, level)) = parse_setext_heading(source, node) {
7343 headings.push(RawHeading {
7344 name,
7345 level,
7346 range: node_range(node),
7347 });
7348 }
7349 } else {
7350 let mut cursor = node.walk();
7351 if cursor.goto_first_child() {
7352 loop {
7353 collect_headings(source, &cursor.node(), headings);
7354 if !cursor.goto_next_sibling() {
7355 break;
7356 }
7357 }
7358 }
7359 }
7360}
7361
7362fn extract_md_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
7366 let mut raw_headings = Vec::new();
7367 collect_headings(source, root, &mut raw_headings);
7368
7369 let mut symbols = Vec::new();
7370 let total_lines = source.lines().count() as u32;
7371
7372 let mut active_headings: Vec<Option<String>> = vec![None; 7];
7373
7374 for i in 0..raw_headings.len() {
7375 let h = &raw_headings[i];
7376 let level = h.level as usize;
7377
7378 let mut scope_chain = Vec::new();
7380 for l in 1..level {
7381 if let Some(ref name) = active_headings[l] {
7382 scope_chain.push(name.clone());
7383 }
7384 }
7385
7386 active_headings[level] = Some(h.name.clone());
7388 for l in (level + 1)..7 {
7389 active_headings[l] = None;
7390 }
7391
7392 let start_line = h.range.start_line;
7394 let start_col = h.range.start_col;
7395
7396 let mut end_line = total_lines.saturating_sub(1);
7398 let mut end_col = if total_lines > 0 {
7399 source_line_end_col(source, end_line)
7400 } else {
7401 0
7402 };
7403
7404 for j in (i + 1)..raw_headings.len() {
7405 if raw_headings[j].level <= h.level {
7406 let next_start_line = raw_headings[j].range.start_line;
7408 end_line = next_start_line.saturating_sub(1).max(start_line);
7409 end_col = source_line_end_col(source, end_line);
7410 break;
7411 }
7412 }
7413
7414 let signature = format!("{} {}", "#".repeat((h.level as usize).min(6)), h.name);
7415
7416 symbols.push(Symbol {
7417 name: h.name.clone(),
7418 kind: SymbolKind::Heading,
7419 range: Range {
7420 start_line,
7421 start_col,
7422 end_line,
7423 end_col,
7424 },
7425 signature: Some(signature),
7426 scope_chain: scope_chain.clone(),
7427 exported: false,
7428 parent: scope_chain.last().cloned(),
7429 });
7430 }
7431
7432 Ok(symbols)
7433}
7434
7435fn dedup_symbols(symbols: &mut Vec<Symbol>) {
7439 let mut seen = std::collections::HashSet::new();
7440 symbols.retain(|s| {
7441 let key = (s.name.clone(), format!("{:?}", s.kind), s.range.start_line);
7442 seen.insert(key)
7443 });
7444}
7445
7446pub struct TreeSitterProvider {
7449 symbol_cache: SharedSymbolCache,
7450}
7451
7452#[derive(Debug, Clone)]
7453struct ReExportTarget {
7454 file: PathBuf,
7455 symbol_name: String,
7456}
7457
7458impl TreeSitterProvider {
7459 pub fn new() -> Self {
7461 Self::with_symbol_cache(Arc::new(RwLock::new(SymbolCache::new())))
7462 }
7463
7464 pub fn with_symbol_cache(symbol_cache: SharedSymbolCache) -> Self {
7466 Self { symbol_cache }
7467 }
7468
7469 pub fn symbol_cache_len(&self) -> usize {
7471 self.symbol_cache
7472 .read()
7473 .map(|cache| cache.len())
7474 .unwrap_or(0)
7475 }
7476
7477 pub fn symbol_cache(&self) -> SharedSymbolCache {
7479 Arc::clone(&self.symbol_cache)
7480 }
7481
7482 fn resolve_symbol_inner(
7483 &self,
7484 parser: &mut FileParser,
7485 file: &Path,
7486 name: &str,
7487 depth: usize,
7488 visited: &mut HashSet<(PathBuf, String)>,
7489 ) -> Result<Vec<SymbolMatch>, AftError> {
7490 if depth > MAX_REEXPORT_DEPTH {
7491 return Ok(Vec::new());
7492 }
7493
7494 let canonical_file = std::fs::canonicalize(file).unwrap_or_else(|_| file.to_path_buf());
7495 if !visited.insert((canonical_file, name.to_string())) {
7496 return Ok(Vec::new());
7497 }
7498
7499 let symbols = parser.extract_symbols(file)?;
7500 let local_matches = symbol_matches_in_file(file, &symbols, name);
7501 if !local_matches.is_empty() {
7502 return Ok(local_matches);
7503 }
7504
7505 if name == "default" {
7506 let default_matches = self.resolve_local_default_export(parser, file, &symbols)?;
7507 if !default_matches.is_empty() {
7508 return Ok(default_matches);
7509 }
7510 }
7511
7512 let reexport_targets = self.collect_reexport_targets(parser, file, name)?;
7513 let mut matches = Vec::new();
7514 let mut seen = HashSet::new();
7515 for target in reexport_targets {
7516 for resolved in self.resolve_symbol_inner(
7517 parser,
7518 &target.file,
7519 &target.symbol_name,
7520 depth + 1,
7521 visited,
7522 )? {
7523 let key = format!(
7524 "{}:{}:{}:{}:{}:{}",
7525 resolved.file,
7526 resolved.symbol.name,
7527 resolved.symbol.range.start_line,
7528 resolved.symbol.range.start_col,
7529 resolved.symbol.range.end_line,
7530 resolved.symbol.range.end_col
7531 );
7532 if seen.insert(key) {
7533 matches.push(resolved);
7534 }
7535 }
7536 }
7537
7538 Ok(matches)
7539 }
7540
7541 fn collect_reexport_targets(
7542 &self,
7543 parser: &mut FileParser,
7544 file: &Path,
7545 requested_name: &str,
7546 ) -> Result<Vec<ReExportTarget>, AftError> {
7547 let (source, tree, lang) = self.read_parsed_file(parser, file)?;
7548 if !matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
7549 return Ok(Vec::new());
7550 }
7551
7552 let mut targets = Vec::new();
7553 let root = tree.root_node();
7554 let from_dir = file.parent().unwrap_or_else(|| Path::new("."));
7555
7556 let mut cursor = root.walk();
7557 if !cursor.goto_first_child() {
7558 return Ok(targets);
7559 }
7560
7561 loop {
7562 let node = cursor.node();
7563 if node.kind() == "export_statement" {
7564 let Some(source_node) = node.child_by_field_name("source") else {
7565 if let Some(export_clause) = find_child_by_kind(node, "export_clause") {
7566 if let Some(symbol_name) =
7567 resolve_export_clause_name(&source, &export_clause, requested_name)
7568 {
7569 targets.push(ReExportTarget {
7570 file: file.to_path_buf(),
7571 symbol_name,
7572 });
7573 }
7574 }
7575 if !cursor.goto_next_sibling() {
7576 break;
7577 }
7578 continue;
7579 };
7580
7581 let Some(module_path) = string_content(&source, &source_node) else {
7582 if !cursor.goto_next_sibling() {
7583 break;
7584 }
7585 continue;
7586 };
7587
7588 let Some(target_file) = resolve_module_path(from_dir, &module_path) else {
7589 if !cursor.goto_next_sibling() {
7590 break;
7591 }
7592 continue;
7593 };
7594
7595 if let Some(export_clause) = find_child_by_kind(node, "export_clause") {
7596 if let Some(symbol_name) =
7597 resolve_export_clause_name(&source, &export_clause, requested_name)
7598 {
7599 targets.push(ReExportTarget {
7600 file: target_file,
7601 symbol_name,
7602 });
7603 }
7604 } else if export_statement_has_wildcard(&source, &node) {
7605 targets.push(ReExportTarget {
7606 file: target_file,
7607 symbol_name: requested_name.to_string(),
7608 });
7609 }
7610 }
7611
7612 if !cursor.goto_next_sibling() {
7613 break;
7614 }
7615 }
7616
7617 Ok(targets)
7618 }
7619
7620 fn resolve_local_default_export(
7621 &self,
7622 parser: &mut FileParser,
7623 file: &Path,
7624 symbols: &[Symbol],
7625 ) -> Result<Vec<SymbolMatch>, AftError> {
7626 let (source, tree, lang) = self.read_parsed_file(parser, file)?;
7627 if !matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
7628 return Ok(Vec::new());
7629 }
7630
7631 let root = tree.root_node();
7632 let mut matches = Vec::new();
7633 let mut seen = HashSet::new();
7634
7635 let mut cursor = root.walk();
7636 if !cursor.goto_first_child() {
7637 return Ok(matches);
7638 }
7639
7640 loop {
7641 let node = cursor.node();
7642 if node.kind() == "export_statement"
7643 && node.child_by_field_name("source").is_none()
7644 && node_contains_token(&source, &node, "default")
7645 {
7646 if let Some(target_name) = default_export_target_name(&source, &node) {
7647 for symbol_match in symbol_matches_in_file(file, symbols, &target_name) {
7648 let key = format!(
7649 "{}:{}:{}:{}:{}:{}",
7650 symbol_match.file,
7651 symbol_match.symbol.name,
7652 symbol_match.symbol.range.start_line,
7653 symbol_match.symbol.range.start_col,
7654 symbol_match.symbol.range.end_line,
7655 symbol_match.symbol.range.end_col
7656 );
7657 if seen.insert(key) {
7658 matches.push(symbol_match);
7659 }
7660 }
7661 }
7662 }
7663
7664 if !cursor.goto_next_sibling() {
7665 break;
7666 }
7667 }
7668
7669 Ok(matches)
7670 }
7671
7672 fn read_parsed_file(
7673 &self,
7674 parser: &mut FileParser,
7675 file: &Path,
7676 ) -> Result<(String, Tree, LangId), AftError> {
7677 let current_mtime = std::fs::metadata(file)
7678 .and_then(|m| m.modified())
7679 .map_err(|e| AftError::FileNotFound {
7680 path: format!("{}: {}", file.display(), e),
7681 })?;
7682 let source = std::fs::read_to_string(file).map_err(|e| AftError::FileNotFound {
7683 path: format!("{}: {}", file.display(), e),
7684 })?;
7685 let size = source.len() as u64;
7686 let content_hash = content_hash_for_source(&source);
7687 let (tree, lang) =
7688 parser.parse_with_source(file, &source, current_mtime, size, content_hash)?;
7689 Ok((source, tree.clone(), lang))
7690 }
7691}
7692
7693fn symbol_matches_in_file(file: &Path, symbols: &[Symbol], name: &str) -> Vec<SymbolMatch> {
7694 symbols
7695 .iter()
7696 .filter(|symbol| symbol_query_matches(symbol, name))
7697 .cloned()
7698 .map(|symbol| SymbolMatch {
7699 file: file.display().to_string(),
7700 symbol,
7701 })
7702 .collect()
7703}
7704
7705fn symbol_query_matches(symbol: &Symbol, query: &str) -> bool {
7706 if symbol.name == query {
7707 return true;
7708 }
7709
7710 if !query.contains('.') && !query.contains("::") {
7711 return false;
7712 }
7713
7714 let mut parts = symbol
7715 .scope_chain
7716 .iter()
7717 .filter(|part| !part.is_empty())
7718 .map(String::as_str)
7719 .collect::<Vec<_>>();
7720 if parts.is_empty() {
7721 return false;
7722 }
7723 parts.push(symbol.name.as_str());
7724
7725 parts.join(".") == query || parts.join("::") == query
7726}
7727
7728fn string_content(source: &str, node: &Node) -> Option<String> {
7729 let text = node_text(source, node);
7730 if text.len() < 2 {
7731 return None;
7732 }
7733
7734 Some(
7735 text.trim_start_matches(|c| c == '\'' || c == '"')
7736 .trim_end_matches(|c| c == '\'' || c == '"')
7737 .to_string(),
7738 )
7739}
7740
7741fn find_child_by_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
7742 let mut cursor = node.walk();
7743 if !cursor.goto_first_child() {
7744 return None;
7745 }
7746
7747 loop {
7748 let child = cursor.node();
7749 if child.kind() == kind {
7750 return Some(child);
7751 }
7752 if !cursor.goto_next_sibling() {
7753 break;
7754 }
7755 }
7756
7757 None
7758}
7759
7760fn resolve_export_clause_name(
7761 source: &str,
7762 export_clause: &Node,
7763 requested_name: &str,
7764) -> Option<String> {
7765 let mut cursor = export_clause.walk();
7766 if !cursor.goto_first_child() {
7767 return None;
7768 }
7769
7770 loop {
7771 let child = cursor.node();
7772 if child.kind() == "export_specifier" {
7773 let (source_name, exported_name) = export_specifier_names(source, &child)?;
7774 if exported_name == requested_name {
7775 return Some(source_name);
7776 }
7777 }
7778
7779 if !cursor.goto_next_sibling() {
7780 break;
7781 }
7782 }
7783
7784 None
7785}
7786
7787fn export_specifier_names(source: &str, specifier: &Node) -> Option<(String, String)> {
7788 let source_name = specifier
7789 .child_by_field_name("name")
7790 .map(|node| node_text(source, &node).to_string());
7791 let alias_name = specifier
7792 .child_by_field_name("alias")
7793 .map(|node| node_text(source, &node).to_string());
7794
7795 if let Some(source_name) = source_name {
7796 let exported_name = alias_name.unwrap_or_else(|| source_name.clone());
7797 return Some((source_name, exported_name));
7798 }
7799
7800 let mut names = Vec::new();
7801 let mut cursor = specifier.walk();
7802 if cursor.goto_first_child() {
7803 loop {
7804 let child = cursor.node();
7805 let child_text = node_text(source, &child).trim();
7806 if matches!(
7807 child.kind(),
7808 "identifier" | "type_identifier" | "property_identifier"
7809 ) || child_text == "default"
7810 {
7811 names.push(child_text.to_string());
7812 }
7813 if !cursor.goto_next_sibling() {
7814 break;
7815 }
7816 }
7817 }
7818
7819 match names.as_slice() {
7820 [name] => Some((name.clone(), name.clone())),
7821 [source_name, exported_name, ..] => Some((source_name.clone(), exported_name.clone())),
7822 _ => None,
7823 }
7824}
7825
7826fn export_statement_has_wildcard(source: &str, node: &Node) -> bool {
7827 let mut cursor = node.walk();
7828 if !cursor.goto_first_child() {
7829 return false;
7830 }
7831
7832 loop {
7833 if node_text(source, &cursor.node()).trim() == "*" {
7834 return true;
7835 }
7836 if !cursor.goto_next_sibling() {
7837 break;
7838 }
7839 }
7840
7841 false
7842}
7843
7844fn node_contains_token(source: &str, node: &Node, token: &str) -> bool {
7845 let mut cursor = node.walk();
7846 if !cursor.goto_first_child() {
7847 return false;
7848 }
7849
7850 loop {
7851 if node_text(source, &cursor.node()).trim() == token {
7852 return true;
7853 }
7854 if !cursor.goto_next_sibling() {
7855 break;
7856 }
7857 }
7858
7859 false
7860}
7861
7862fn default_export_target_name(source: &str, export_stmt: &Node) -> Option<String> {
7863 if let Some(value_node) = export_stmt.child_by_field_name("value") {
7864 if let Some(name) = default_export_node_name(source, &value_node) {
7865 return Some(name);
7866 }
7867 }
7868
7869 if let Some(declaration_node) = export_stmt.child_by_field_name("declaration") {
7870 if let Some(name) = default_export_node_name(source, &declaration_node) {
7871 return Some(name);
7872 }
7873 }
7874
7875 let mut cursor = export_stmt.walk();
7876 if !cursor.goto_first_child() {
7877 return None;
7878 }
7879
7880 loop {
7881 let child = cursor.node();
7882 if let Some(name) = default_export_node_name(source, &child) {
7883 return Some(name);
7884 }
7885
7886 if !cursor.goto_next_sibling() {
7887 break;
7888 }
7889 }
7890
7891 None
7892}
7893
7894fn default_export_node_name(source: &str, node: &Node) -> Option<String> {
7895 match node.kind() {
7896 "function_declaration"
7897 | "generator_function_declaration"
7898 | "function_expression"
7899 | "generator_function"
7900 | "class_declaration"
7901 | "class" => node
7902 .child_by_field_name("name")
7903 .map(|name_node| node_text(source, &name_node).to_string())
7904 .or_else(|| Some("default".to_string())),
7905 "interface_declaration" | "enum_declaration" | "type_alias_declaration" => node
7906 .child_by_field_name("name")
7907 .map(|name_node| node_text(source, &name_node).to_string()),
7908 "lexical_declaration" => lexical_declaration_name(source, node),
7909 "identifier" | "type_identifier" => {
7910 let text = node_text(source, node);
7911 (text != "export" && text != "default").then(|| text.to_string())
7912 }
7913 _ => None,
7914 }
7915}
7916
7917fn lexical_declaration_name(source: &str, node: &Node) -> Option<String> {
7918 let mut cursor = node.walk();
7919 if !cursor.goto_first_child() {
7920 return None;
7921 }
7922
7923 loop {
7924 let child = cursor.node();
7925 if child.kind() == "variable_declarator" {
7926 if let Some(name_node) = child.child_by_field_name("name") {
7927 return Some(node_text(source, &name_node).to_string());
7928 }
7929 }
7930 if !cursor.goto_next_sibling() {
7931 break;
7932 }
7933 }
7934
7935 None
7936}
7937
7938impl crate::language::LanguageProvider for TreeSitterProvider {
7939 fn resolve_symbol(&self, file: &Path, name: &str) -> Result<Vec<SymbolMatch>, AftError> {
7940 let mut parser = FileParser::with_symbol_cache(self.symbol_cache());
7941 let matches = self.resolve_symbol_inner(&mut parser, file, name, 0, &mut HashSet::new())?;
7942
7943 if matches.is_empty() {
7944 Err(AftError::SymbolNotFound {
7945 name: name.to_string(),
7946 file: file.display().to_string(),
7947 })
7948 } else {
7949 Ok(matches)
7950 }
7951 }
7952
7953 fn list_symbols(&self, file: &Path) -> Result<Vec<Symbol>, AftError> {
7954 let mut parser = FileParser::with_symbol_cache(self.symbol_cache());
7955 parser.extract_symbols(file)
7956 }
7957
7958 fn heading_anchors(
7959 &self,
7960 file: &Path,
7961 ) -> Result<Vec<crate::language::HeadingAnchor>, AftError> {
7962 if detect_language(file) != Some(LangId::Html) {
7963 return Ok(Vec::new());
7964 }
7965 let mut parser = FileParser::with_symbol_cache(self.symbol_cache());
7966 let (source, tree, _) = self.read_parsed_file(&mut parser, file)?;
7967 Ok(extract_html_heading_anchors(&source, &tree.root_node()))
7968 }
7969
7970 fn as_any(&self) -> &dyn std::any::Any {
7971 self
7972 }
7973}
7974
7975#[cfg(test)]
7976mod tests {
7977 use super::*;
7978 use crate::language::LanguageProvider;
7979 use crate::symbol_cache_disk;
7980 use std::path::{Path, PathBuf};
7981
7982 fn fixture_path(name: &str) -> PathBuf {
7983 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
7984 .join("tests")
7985 .join("fixtures")
7986 .join(name)
7987 }
7988
7989 #[test]
7990 #[ignore = "manual single-file parser/query phase benchmark"]
7991 fn profile_rust_parser_and_query_phases() {
7992 let source = "pub fn tiny() -> bool {\n true\n}\n";
7993
7994 let grammar_started = std::time::Instant::now();
7995 let grammar = grammar_for(LangId::Rust);
7996 let grammar_elapsed = grammar_started.elapsed();
7997
7998 let parser_init_started = std::time::Instant::now();
7999 let mut parser = Parser::new();
8000 parser.set_language(&grammar).unwrap();
8001 let parser_init_elapsed = parser_init_started.elapsed();
8002
8003 let parse_started = std::time::Instant::now();
8004 let tree = parser.parse(source, None).unwrap();
8005 let parse_elapsed = parse_started.elapsed();
8006
8007 let query_compile_started = std::time::Instant::now();
8008 let query = Query::new(&grammar, RS_QUERY).unwrap();
8009 let query_compile_elapsed = query_compile_started.elapsed();
8010
8011 let query_execute_started = std::time::Instant::now();
8012 let mut cursor = QueryCursor::new();
8013 let mut matches = cursor.matches(&query, tree.root_node(), source.as_bytes());
8014 let mut query_matches = 0;
8015 while matches.next().is_some() {
8016 query_matches += 1;
8017 }
8018 let query_execute_elapsed = query_execute_started.elapsed();
8019
8020 let direct_extract_started = std::time::Instant::now();
8021 let symbols = extract_rs_symbols(source, &tree.root_node()).unwrap();
8022 let direct_extract_elapsed = direct_extract_started.elapsed();
8023
8024 eprintln!(
8025 "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={}",
8026 symbols.len()
8027 );
8028 assert!(query_matches > 0);
8029 assert_eq!(symbols.len(), 1);
8030 }
8031
8032 fn extract_rs_symbols_with_query_oracle(source: &str, root: Node<'_>) -> Vec<Symbol> {
8033 let grammar = grammar_for(LangId::Rust);
8034 let query = Query::new(&grammar, RS_QUERY).expect("compile Rust parity query");
8035 let capture_names = query.capture_names();
8036 let is_pub = |node: &Node<'_>| {
8037 let mut cursor = node.walk();
8038 if cursor.goto_first_child() {
8039 loop {
8040 if cursor.node().kind() == "visibility_modifier" {
8041 return true;
8042 }
8043 if !cursor.goto_next_sibling() {
8044 break;
8045 }
8046 }
8047 }
8048 false
8049 };
8050
8051 let mut symbols = Vec::new();
8052 let mut cursor = QueryCursor::new();
8053 let mut matches = cursor.matches(&query, root, source.as_bytes());
8054 while let Some(query_match) = {
8055 matches.advance();
8056 matches.get()
8057 } {
8058 let mut function_name = None;
8059 let mut function_definition = None;
8060 let mut item_name = None;
8061 let mut item_definition = None;
8062 let mut item_kind = None;
8063 let mut impl_node = None;
8064 for capture in query_match.captures {
8065 let Some(name) = capture_names.get(capture.index as usize) else {
8066 continue;
8067 };
8068 match *name {
8069 "fn.name" => function_name = Some(capture.node),
8070 "fn.def" => function_definition = Some(capture.node),
8071 "struct.name" => {
8072 item_name = Some(capture.node);
8073 item_kind = Some(SymbolKind::Struct);
8074 }
8075 "struct.def" => item_definition = Some(capture.node),
8076 "enum.name" => {
8077 item_name = Some(capture.node);
8078 item_kind = Some(SymbolKind::Enum);
8079 }
8080 "enum.def" => item_definition = Some(capture.node),
8081 "trait.name" => {
8082 item_name = Some(capture.node);
8083 item_kind = Some(SymbolKind::Interface);
8084 }
8085 "trait.def" => item_definition = Some(capture.node),
8086 "impl.def" => impl_node = Some(capture.node),
8087 _ => {}
8088 }
8089 }
8090
8091 if let (Some(name_node), Some(definition)) = (function_name, function_definition) {
8092 let owner = rust_function_declaration_list_owner(&definition);
8093 if owner
8094 .as_ref()
8095 .is_none_or(|owner| owner.kind() == "mod_item")
8096 {
8097 let scope_chain = rust_mod_scope_chain(&definition, source);
8098 symbols.push(Symbol {
8099 name: node_text(source, &name_node).to_string(),
8100 kind: SymbolKind::Function,
8101 range: node_range_with_decorators(&definition, source, LangId::Rust),
8102 signature: Some(extract_signature(source, &definition)),
8103 scope_chain: scope_chain.clone(),
8104 exported: is_pub(&definition),
8105 parent: scope_chain.last().cloned(),
8106 });
8107 }
8108 }
8109
8110 if let (Some(name_node), Some(definition), Some(kind)) =
8111 (item_name, item_definition, item_kind)
8112 {
8113 symbols.push(Symbol {
8114 name: node_text(source, &name_node).to_string(),
8115 kind,
8116 range: node_range_with_decorators(&definition, source, LangId::Rust),
8117 signature: Some(extract_signature(source, &definition)),
8118 scope_chain: Vec::new(),
8119 exported: is_pub(&definition),
8120 parent: None,
8121 });
8122 }
8123
8124 if let Some(impl_node) = impl_node {
8125 let scope_name = rust_impl_scope_name(&impl_node, source);
8126 let parent_name = scope_name
8127 .rsplit(" for ")
8128 .next()
8129 .unwrap_or_default()
8130 .to_string();
8131 let mut impl_cursor = impl_node.walk();
8132 if impl_cursor.goto_first_child() {
8133 loop {
8134 let child = impl_cursor.node();
8135 if child.kind() == "declaration_list" {
8136 let mut method_cursor = child.walk();
8137 if method_cursor.goto_first_child() {
8138 loop {
8139 let method = method_cursor.node();
8140 if method.kind() == "function_item" {
8141 if let Some(name_node) = method.child_by_field_name("name")
8142 {
8143 symbols.push(Symbol {
8144 name: node_text(source, &name_node).to_string(),
8145 kind: SymbolKind::Method,
8146 range: node_range_with_decorators(
8147 &method,
8148 source,
8149 LangId::Rust,
8150 ),
8151 signature: Some(extract_signature(source, &method)),
8152 scope_chain: (!scope_name.is_empty())
8153 .then(|| vec![scope_name.clone()])
8154 .unwrap_or_default(),
8155 exported: is_pub(&method),
8156 parent: (!parent_name.is_empty())
8157 .then(|| parent_name.clone()),
8158 });
8159 }
8160 }
8161 if !method_cursor.goto_next_sibling() {
8162 break;
8163 }
8164 }
8165 }
8166 }
8167 if !impl_cursor.goto_next_sibling() {
8168 break;
8169 }
8170 }
8171 }
8172 }
8173 }
8174
8175 dedup_symbols(&mut symbols);
8176 symbols
8177 }
8178
8179 fn rust_symbol_fingerprint(symbols: &[Symbol]) -> Vec<String> {
8180 symbols
8181 .iter()
8182 .map(|symbol| {
8183 format!(
8184 "{}|{:?}|{:?}|{:?}|{:?}|{}|{:?}",
8185 symbol.name,
8186 symbol.kind,
8187 symbol.range,
8188 symbol.signature,
8189 symbol.scope_chain,
8190 symbol.exported,
8191 symbol.parent
8192 )
8193 })
8194 .collect()
8195 }
8196
8197 #[test]
8198 fn rust_walk_matches_query_oracle_for_adversarial_shapes() {
8199 let source = r#"
8200#[cfg(any())]
8201pub(crate) mod outer {
8202 pub mod inner {
8203 #[doc = "keeps decorator range"]
8204 pub(crate) fn nested<T>() {}
8205
8206 pub(crate) struct Generic<T>(T);
8207 pub enum State { Ready }
8208 pub trait Service<T> {
8209 fn declaration(&self);
8210 }
8211
8212 impl<T> Service<T> for Generic<T> {
8213 #[inline]
8214 pub(crate) fn attributed_method(&self) {}
8215 fn private_method(&self) {}
8216 }
8217 }
8218}
8219"#;
8220 let grammar = grammar_for(LangId::Rust);
8221 let mut parser = Parser::new();
8222 parser.set_language(&grammar).unwrap();
8223 let tree = parser.parse(source, None).unwrap();
8224
8225 let query_symbols = extract_rs_symbols_with_query_oracle(source, tree.root_node());
8226 let walk_symbols = extract_rs_symbols(source, &tree.root_node()).unwrap();
8227 assert_eq!(
8228 rust_symbol_fingerprint(&walk_symbols),
8229 rust_symbol_fingerprint(&query_symbols)
8230 );
8231 assert!(walk_symbols.iter().any(|symbol| {
8232 symbol.name == "nested" && symbol.exported && symbol.scope_chain == ["outer", "inner"]
8233 }));
8234 assert!(walk_symbols.iter().any(|symbol| {
8235 symbol.name == "attributed_method"
8236 && symbol.kind == SymbolKind::Method
8237 && symbol.scope_chain == ["Service<T> for Generic<T>"]
8238 && symbol.exported
8239 }));
8240 assert!(
8241 walk_symbols
8242 .iter()
8243 .all(|symbol| symbol.name != "declaration"),
8244 "trait declarations are not implementation methods"
8245 );
8246 }
8247
8248 #[test]
8249 fn source_parser_is_reused_on_the_current_worker_thread() {
8250 let first = "pub fn first() {}\n";
8251 let second = "pub struct Second;\n";
8252 let path = Path::new("worker.rs");
8253
8254 let before = REUSABLE_PARSERS.with(|parsers| parsers.borrow().len());
8255 let first_tree =
8256 parse_source_with_cached_parser(path, first, LangId::Rust).expect("parse first source");
8257 let after_first = REUSABLE_PARSERS.with(|parsers| parsers.borrow().len());
8258 let second_tree = parse_source_with_cached_parser(path, second, LangId::Rust)
8259 .expect("parse second source");
8260 let after_second = REUSABLE_PARSERS.with(|parsers| parsers.borrow().len());
8261
8262 assert!(!first_tree.root_node().has_error());
8263 assert!(!second_tree.root_node().has_error());
8264 assert!(after_first == before || after_first == before + 1);
8265 assert_eq!(after_second, after_first);
8266 }
8267
8268 fn test_symbol(name: &str) -> Symbol {
8269 Symbol {
8270 name: name.to_string(),
8271 kind: SymbolKind::Function,
8272 range: Range {
8273 start_line: 0,
8274 start_col: 0,
8275 end_line: 0,
8276 end_col: 10,
8277 },
8278 signature: Some(format!("fn {name}()")),
8279 scope_chain: Vec::new(),
8280 exported: true,
8281 parent: None,
8282 }
8283 }
8284
8285 #[test]
8286 fn symbol_memory_estimate_is_zero_when_empty_and_nonzero_when_populated() {
8287 let mut cache = SymbolCache::new();
8288 assert_eq!(cache.estimated_memory().estimated_bytes, Some(0));
8289 cache.insert(
8290 PathBuf::from("src/lib.rs"),
8291 SystemTime::UNIX_EPOCH,
8292 10,
8293 cache_freshness::zero_hash(),
8294 vec![test_symbol("memory_estimate")],
8295 );
8296 let estimate = cache.estimated_memory();
8297 assert!(estimate.estimated_bytes.unwrap() > 0);
8298 assert_eq!(estimate.counts["entries"], 1);
8299 assert_eq!(estimate.counts["symbols"], 1);
8300 }
8301
8302 #[test]
8303 fn inc_and_scss_extensions_are_detected() {
8304 assert_eq!(
8305 detect_language(Path::new("template.inc")),
8306 Some(LangId::Php)
8307 );
8308 assert_eq!(
8309 detect_language(Path::new("styles.scss")),
8310 Some(LangId::Scss)
8311 );
8312 assert_eq!(detect_language(Path::new("main.pas")), Some(LangId::Pascal));
8313 assert_eq!(detect_language(Path::new("main.pp")), Some(LangId::Pascal));
8314 assert_eq!(detect_language(Path::new("main.dpr")), Some(LangId::Pascal));
8315 assert_eq!(detect_language(Path::new("main.dpk")), Some(LangId::Pascal));
8316 assert_eq!(detect_language(Path::new("main.lpr")), Some(LangId::Pascal));
8317 assert_eq!(detect_language(Path::new("template.tpl")), None);
8318 }
8319
8320 #[test]
8321 fn inc_files_parse_with_php_grammar() {
8322 let tmp = tempfile::tempdir().expect("create temp dir");
8323 let file = tmp.path().join("partial.inc");
8324 std::fs::write(&file, "<?php\nfunction render_partial() { return 1; }\n")
8325 .expect("write inc file");
8326
8327 let mut parser = FileParser::new();
8328 let symbols = parser
8329 .extract_symbols(&file)
8330 .expect("extract php inc symbols");
8331 let function = symbols
8332 .iter()
8333 .find(|symbol| symbol.name == "render_partial")
8334 .expect("find PHP function in .inc");
8335 assert_eq!(function.kind, SymbolKind::Function);
8336 }
8337
8338 #[test]
8339 fn scss_symbols_include_mixin_variable_function_and_rule() {
8340 let tmp = tempfile::tempdir().expect("create temp dir");
8341 let file = tmp.path().join("styles.scss");
8342 std::fs::write(
8343 &file,
8344 r#"$brand-color: #336699;
8345
8346@mixin button-base($padding) {
8347 padding: $padding;
8348}
8349
8350@function double($value) {
8351 @return $value * 2;
8352}
8353
8354.card, .panel {
8355 color: $brand-color;
8356}
8357"#,
8358 )
8359 .expect("write scss file");
8360
8361 let mut parser = FileParser::new();
8362 let symbols = parser.extract_symbols(&file).expect("extract scss symbols");
8363 let get = |name: &str| {
8364 symbols
8365 .iter()
8366 .find(|symbol| symbol.name == name)
8367 .unwrap_or_else(|| panic!("missing {name}; got {symbols:?}"))
8368 };
8369
8370 assert_eq!(get("button-base").kind, SymbolKind::Function);
8371 assert_eq!(get("double").kind, SymbolKind::Function);
8372 assert_eq!(get("$brand-color").kind, SymbolKind::Variable);
8373 assert_eq!(get(".card, .panel").kind, SymbolKind::Class);
8374 }
8375
8376 #[test]
8377 fn symbol_cache_load_from_disk_round_trips_synthetic_entry() {
8378 let project = tempfile::tempdir().expect("create project dir");
8379 let storage = tempfile::tempdir().expect("create storage dir");
8380 let source = project.path().join("src/lib.rs");
8381 std::fs::create_dir_all(source.parent().expect("source parent"))
8382 .expect("create source dir");
8383 std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
8384 let mtime = std::fs::metadata(&source)
8385 .expect("stat source")
8386 .modified()
8387 .expect("source mtime");
8388 let content = std::fs::read(&source).expect("read source");
8389 let size = content.len() as u64;
8390 let hash = crate::cache_freshness::hash_bytes(&content);
8391
8392 let mut cache = SymbolCache::new();
8393 cache.set_project_root(project.path().to_path_buf());
8394 cache.insert(
8395 source.clone(),
8396 mtime,
8397 size,
8398 hash,
8399 vec![test_symbol("cached")],
8400 );
8401 symbol_cache_disk::write_to_disk(&cache, storage.path(), "unit-project")
8402 .expect("write symbol cache");
8403
8404 let mut restored = SymbolCache::new();
8405 let outcome =
8406 restored.load_from_disk_with_outcome(storage.path(), "unit-project", project.path());
8407 let symbols = restored.get(&source, mtime).expect("restored symbols");
8408
8409 assert_eq!(outcome.loaded, 1);
8410 assert!(!outcome.needs_persistence);
8411 assert_eq!(symbols.len(), 1);
8412 assert_eq!(symbols[0].name, "cached");
8413 }
8414
8415 #[test]
8416 fn symbol_cache_load_from_disk_marks_mtime_refresh_for_persistence() {
8417 let project = tempfile::tempdir().expect("create project dir");
8418 let storage = tempfile::tempdir().expect("create storage dir");
8419 let source = project.path().join("src/lib.rs");
8420 std::fs::create_dir_all(source.parent().expect("source parent"))
8421 .expect("create source dir");
8422 std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
8423 let metadata = std::fs::metadata(&source).expect("stat source");
8424 let mtime = metadata.modified().expect("source mtime");
8425 let content = std::fs::read(&source).expect("read source");
8426
8427 let mut cache = SymbolCache::new();
8428 cache.set_project_root(project.path().to_path_buf());
8429 cache.insert(
8430 source.clone(),
8431 mtime,
8432 content.len() as u64,
8433 crate::cache_freshness::hash_bytes(&content),
8434 vec![test_symbol("cached")],
8435 );
8436 symbol_cache_disk::write_to_disk(&cache, storage.path(), "mtime-unit-project")
8437 .expect("write symbol cache");
8438
8439 let advanced_mtime = mtime
8440 .checked_add(std::time::Duration::from_secs(2))
8441 .expect("advance source mtime");
8442 filetime::set_file_mtime(
8443 &source,
8444 filetime::FileTime::from_system_time(advanced_mtime),
8445 )
8446 .expect("touch source");
8447
8448 let mut restored = SymbolCache::new();
8449 let outcome = restored.load_from_disk_with_outcome(
8450 storage.path(),
8451 "mtime-unit-project",
8452 project.path(),
8453 );
8454
8455 assert_eq!(outcome.loaded, 1);
8456 assert!(outcome.needs_persistence);
8457 assert!(restored.get(&source, advanced_mtime).is_some());
8458 }
8459
8460 #[test]
8461 fn symbol_cache_load_from_disk_drops_stale_synthetic_entry() {
8462 let project = tempfile::tempdir().expect("create project dir");
8463 let storage = tempfile::tempdir().expect("create storage dir");
8464 let source = project.path().join("src/lib.rs");
8465 std::fs::create_dir_all(source.parent().expect("source parent"))
8466 .expect("create source dir");
8467 std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
8468 let mtime = std::fs::metadata(&source)
8469 .expect("stat source")
8470 .modified()
8471 .expect("source mtime");
8472 let content = std::fs::read(&source).expect("read source");
8473 let size = content.len() as u64;
8474 let hash = crate::cache_freshness::hash_bytes(&content);
8475
8476 let mut cache = SymbolCache::new();
8477 cache.set_project_root(project.path().to_path_buf());
8478 cache.insert(
8479 source.clone(),
8480 mtime,
8481 size,
8482 hash,
8483 vec![test_symbol("cached")],
8484 );
8485 symbol_cache_disk::write_to_disk(&cache, storage.path(), "stale-unit-project")
8486 .expect("write symbol cache");
8487
8488 std::fs::write(&source, "pub fn cached() {}\npub fn fresh() {}\n").expect("change source");
8489
8490 let mut restored = SymbolCache::new();
8491 let outcome = restored.load_from_disk_with_outcome(
8492 storage.path(),
8493 "stale-unit-project",
8494 project.path(),
8495 );
8496
8497 assert_eq!(outcome.loaded, 0);
8498 assert!(outcome.needs_persistence);
8499 assert_eq!(restored.len(), 0);
8500 }
8501
8502 #[test]
8503 fn stale_prewarm_generation_cannot_repopulate_symbol_cache_after_reset() {
8504 let project = tempfile::tempdir().expect("create project dir");
8505 let storage = tempfile::tempdir().expect("create storage dir");
8506 let source = project.path().join("src/lib.rs");
8507 std::fs::create_dir_all(source.parent().expect("source parent"))
8508 .expect("create source dir");
8509 std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
8510 let mtime = std::fs::metadata(&source)
8511 .expect("stat source")
8512 .modified()
8513 .expect("source mtime");
8514 let content = std::fs::read(&source).expect("read source");
8515 let size = content.len() as u64;
8516 let hash = crate::cache_freshness::hash_bytes(&content);
8517
8518 let mut disk_cache = SymbolCache::new();
8519 disk_cache.set_project_root(project.path().to_path_buf());
8520 disk_cache.insert(
8521 source.clone(),
8522 mtime,
8523 size,
8524 hash,
8525 vec![test_symbol("cached")],
8526 );
8527 symbol_cache_disk::write_to_disk(&disk_cache, storage.path(), "prewarm-reset")
8528 .expect("write symbol cache");
8529
8530 let shared = Arc::new(RwLock::new(SymbolCache::new()));
8531 let stale_generation = shared.write().unwrap().reset();
8532 let active_generation = shared.write().unwrap().reset();
8533 assert_ne!(stale_generation, active_generation);
8534
8535 {
8536 let mut cache = shared.write().unwrap();
8537 assert!(!cache
8538 .set_project_root_for_generation(stale_generation, project.path().to_path_buf()));
8539 assert_eq!(
8540 cache.load_from_disk_for_generation(
8541 stale_generation,
8542 storage.path(),
8543 "prewarm-reset",
8544 project.path()
8545 ),
8546 0
8547 );
8548 }
8549
8550 let mut stale_parser =
8551 FileParser::with_symbol_cache_generation(Arc::clone(&shared), Some(stale_generation));
8552 stale_parser
8553 .extract_symbols(&source)
8554 .expect("stale prewarm parses source but must not write cache");
8555
8556 let cache = shared.read().unwrap();
8557 assert_eq!(cache.generation(), active_generation);
8558 assert_eq!(cache.len(), 0);
8559 assert!(cache.project_root().is_none());
8560 assert!(!cache.contains_key(&source));
8561 }
8562
8563 #[test]
8566 fn detect_ts() {
8567 assert_eq!(
8568 detect_language(Path::new("foo.ts")),
8569 Some(LangId::TypeScript)
8570 );
8571 }
8572
8573 #[test]
8574 fn detect_tsx() {
8575 assert_eq!(detect_language(Path::new("foo.tsx")), Some(LangId::Tsx));
8576 }
8577
8578 #[test]
8579 fn detect_js() {
8580 assert_eq!(
8581 detect_language(Path::new("foo.js")),
8582 Some(LangId::JavaScript)
8583 );
8584 }
8585
8586 #[test]
8587 fn detect_jsx() {
8588 assert_eq!(
8589 detect_language(Path::new("foo.jsx")),
8590 Some(LangId::JavaScript)
8591 );
8592 }
8593
8594 #[test]
8595 fn detect_py() {
8596 assert_eq!(detect_language(Path::new("foo.py")), Some(LangId::Python));
8597 }
8598
8599 #[test]
8600 fn detect_rs() {
8601 assert_eq!(detect_language(Path::new("foo.rs")), Some(LangId::Rust));
8602 }
8603
8604 #[test]
8605 fn detect_go() {
8606 assert_eq!(detect_language(Path::new("foo.go")), Some(LangId::Go));
8607 }
8608
8609 #[test]
8610 fn detect_c() {
8611 assert_eq!(detect_language(Path::new("foo.c")), Some(LangId::C));
8612 }
8613
8614 #[test]
8615 fn detect_h() {
8616 assert_eq!(detect_language(Path::new("foo.h")), Some(LangId::C));
8617 }
8618
8619 #[test]
8620 fn detect_cc() {
8621 assert_eq!(detect_language(Path::new("foo.cc")), Some(LangId::Cpp));
8622 }
8623
8624 #[test]
8625 fn detect_cpp() {
8626 assert_eq!(detect_language(Path::new("foo.cpp")), Some(LangId::Cpp));
8627 }
8628
8629 #[test]
8630 fn detect_cxx() {
8631 assert_eq!(detect_language(Path::new("foo.cxx")), Some(LangId::Cpp));
8632 }
8633
8634 #[test]
8635 fn detect_hpp() {
8636 assert_eq!(detect_language(Path::new("foo.hpp")), Some(LangId::Cpp));
8637 }
8638
8639 #[test]
8640 fn detect_hh() {
8641 assert_eq!(detect_language(Path::new("foo.hh")), Some(LangId::Cpp));
8642 }
8643
8644 #[test]
8645 fn detect_zig() {
8646 assert_eq!(detect_language(Path::new("foo.zig")), Some(LangId::Zig));
8647 }
8648
8649 #[test]
8650 fn detect_cs() {
8651 assert_eq!(detect_language(Path::new("foo.cs")), Some(LangId::CSharp));
8652 }
8653
8654 #[test]
8655 fn detect_unknown_returns_none() {
8656 assert_eq!(detect_language(Path::new("foo.txt")), None);
8657 }
8658
8659 #[test]
8660 fn groovy_language_constant_loads_under_workspace_tree_sitter() {
8661 let mut parser = Parser::new();
8662 let language: Language = dekobon_tree_sitter_groovy::LANGUAGE.into();
8663 parser
8664 .set_language(&language)
8665 .expect("load Groovy grammar under workspace tree-sitter");
8666 let tree = parser
8667 .parse("class Greeter { def greet() { 'hi' } }", None)
8668 .expect("parse Groovy sample");
8669 assert!(
8670 !tree.root_node().has_error(),
8671 "Groovy sample should parse cleanly"
8672 );
8673 }
8674
8675 #[test]
8676 fn detect_groovy_extensions_and_jenkinsfile() {
8677 assert_eq!(
8678 detect_language(Path::new("script.groovy")),
8679 Some(LangId::Groovy)
8680 );
8681 assert_eq!(
8682 detect_language(Path::new("script.gvy")),
8683 Some(LangId::Groovy)
8684 );
8685 assert_eq!(
8686 detect_language(Path::new("script.gy")),
8687 Some(LangId::Groovy)
8688 );
8689 assert_eq!(
8690 detect_language(Path::new("shell.gsh")),
8691 Some(LangId::Groovy)
8692 );
8693 assert_eq!(
8694 detect_language(Path::new("build.gradle")),
8695 Some(LangId::Groovy)
8696 );
8697 assert_eq!(
8698 detect_language(Path::new("build.gradle.kts")),
8699 Some(LangId::Kotlin)
8700 );
8701 assert_eq!(
8702 detect_language(Path::new("Jenkinsfile")),
8703 Some(LangId::Groovy)
8704 );
8705 }
8706
8707 #[test]
8710 fn unsupported_extension_returns_invalid_request() {
8711 let path = fixture_path("sample.ts");
8713 let bad_path = path.with_extension("txt");
8714 std::fs::write(&bad_path, "hello").unwrap();
8716 let provider = TreeSitterProvider::new();
8717 let result = provider.list_symbols(&bad_path);
8718 std::fs::remove_file(&bad_path).ok();
8719 match result {
8720 Err(AftError::InvalidRequest { message }) => {
8721 assert!(
8722 message.contains("unsupported file extension"),
8723 "msg: {}",
8724 message
8725 );
8726 assert!(message.contains("txt"), "msg: {}", message);
8727 }
8728 other => panic!("expected InvalidRequest, got {:?}", other),
8729 }
8730 }
8731
8732 #[test]
8735 fn ts_extracts_all_symbol_kinds() {
8736 let provider = TreeSitterProvider::new();
8737 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
8738
8739 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
8740 assert!(
8741 names.contains(&"greet"),
8742 "missing function greet: {:?}",
8743 names
8744 );
8745 assert!(names.contains(&"add"), "missing arrow fn add: {:?}", names);
8746 assert!(
8747 names.contains(&"UserService"),
8748 "missing class UserService: {:?}",
8749 names
8750 );
8751 assert!(
8752 names.contains(&"Config"),
8753 "missing interface Config: {:?}",
8754 names
8755 );
8756 assert!(
8757 names.contains(&"Status"),
8758 "missing enum Status: {:?}",
8759 names
8760 );
8761 assert!(
8762 names.contains(&"UserId"),
8763 "missing type alias UserId: {:?}",
8764 names
8765 );
8766 assert!(
8767 names.contains(&"internalHelper"),
8768 "missing non-exported fn: {:?}",
8769 names
8770 );
8771
8772 assert!(
8774 symbols.len() >= 6,
8775 "expected ≥6 symbols, got {}: {:?}",
8776 symbols.len(),
8777 names
8778 );
8779 }
8780
8781 #[test]
8782 fn ts_symbol_kinds_correct() {
8783 let provider = TreeSitterProvider::new();
8784 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
8785
8786 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
8787
8788 assert_eq!(find("greet").kind, SymbolKind::Function);
8789 assert_eq!(find("add").kind, SymbolKind::Function); assert_eq!(find("UserService").kind, SymbolKind::Class);
8791 assert_eq!(find("Config").kind, SymbolKind::Interface);
8792 assert_eq!(find("Status").kind, SymbolKind::Enum);
8793 assert_eq!(find("UserId").kind, SymbolKind::TypeAlias);
8794 }
8795
8796 #[test]
8797 fn ts_export_detection() {
8798 let provider = TreeSitterProvider::new();
8799 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
8800
8801 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
8802
8803 assert!(find("greet").exported, "greet should be exported");
8804 assert!(find("add").exported, "add should be exported");
8805 assert!(
8806 find("UserService").exported,
8807 "UserService should be exported"
8808 );
8809 assert!(find("Config").exported, "Config should be exported");
8810 assert!(find("Status").exported, "Status should be exported");
8811 assert!(
8812 !find("internalHelper").exported,
8813 "internalHelper should not be exported"
8814 );
8815 }
8816
8817 #[test]
8818 fn ts_method_scope_chain() {
8819 let provider = TreeSitterProvider::new();
8820 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
8821
8822 let methods: Vec<&Symbol> = symbols
8823 .iter()
8824 .filter(|s| s.kind == SymbolKind::Method)
8825 .collect();
8826 assert!(!methods.is_empty(), "should have at least one method");
8827
8828 for method in &methods {
8829 assert_eq!(
8830 method.scope_chain,
8831 vec!["UserService"],
8832 "method {} should have UserService in scope chain",
8833 method.name
8834 );
8835 assert_eq!(method.parent.as_deref(), Some("UserService"));
8836 }
8837 }
8838
8839 #[test]
8840 fn ts_signatures_present() {
8841 let provider = TreeSitterProvider::new();
8842 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
8843
8844 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
8845
8846 let greet_sig = find("greet").signature.as_ref().unwrap();
8847 assert!(
8848 greet_sig.contains("greet"),
8849 "signature should contain function name: {}",
8850 greet_sig
8851 );
8852 }
8853
8854 #[test]
8855 fn ts_ranges_valid() {
8856 let provider = TreeSitterProvider::new();
8857 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
8858
8859 for s in &symbols {
8860 assert!(
8861 s.range.end_line >= s.range.start_line,
8862 "symbol {} has invalid range: {:?}",
8863 s.name,
8864 s.range
8865 );
8866 }
8867 }
8868
8869 #[test]
8872 fn js_extracts_core_symbols() {
8873 let provider = TreeSitterProvider::new();
8874 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
8875
8876 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
8877 assert!(
8878 names.contains(&"multiply"),
8879 "missing function multiply: {:?}",
8880 names
8881 );
8882 assert!(
8883 names.contains(&"divide"),
8884 "missing arrow fn divide: {:?}",
8885 names
8886 );
8887 assert!(
8888 names.contains(&"EventEmitter"),
8889 "missing class EventEmitter: {:?}",
8890 names
8891 );
8892 assert!(
8893 names.contains(&"main"),
8894 "missing default export fn main: {:?}",
8895 names
8896 );
8897
8898 assert!(
8899 symbols.len() >= 4,
8900 "expected ≥4 symbols, got {}: {:?}",
8901 symbols.len(),
8902 names
8903 );
8904 }
8905
8906 #[test]
8907 fn js_arrow_fn_correctly_named() {
8908 let provider = TreeSitterProvider::new();
8909 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
8910
8911 let divide = symbols.iter().find(|s| s.name == "divide").unwrap();
8912 assert_eq!(divide.kind, SymbolKind::Function);
8913 assert!(divide.exported, "divide should be exported");
8914
8915 let internal = symbols.iter().find(|s| s.name == "internalUtil").unwrap();
8916 assert_eq!(internal.kind, SymbolKind::Function);
8917 assert!(!internal.exported, "internalUtil should not be exported");
8918 }
8919
8920 #[test]
8921 fn js_method_scope_chain() {
8922 let provider = TreeSitterProvider::new();
8923 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
8924
8925 let methods: Vec<&Symbol> = symbols
8926 .iter()
8927 .filter(|s| s.kind == SymbolKind::Method)
8928 .collect();
8929
8930 for method in &methods {
8931 assert_eq!(
8932 method.scope_chain,
8933 vec!["EventEmitter"],
8934 "method {} should have EventEmitter in scope chain",
8935 method.name
8936 );
8937 }
8938 }
8939
8940 #[test]
8943 fn tsx_extracts_react_component() {
8944 let provider = TreeSitterProvider::new();
8945 let symbols = provider.list_symbols(&fixture_path("sample.tsx")).unwrap();
8946
8947 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
8948 assert!(
8949 names.contains(&"Button"),
8950 "missing React component Button: {:?}",
8951 names
8952 );
8953 assert!(
8954 names.contains(&"Counter"),
8955 "missing class Counter: {:?}",
8956 names
8957 );
8958 assert!(
8959 names.contains(&"formatLabel"),
8960 "missing function formatLabel: {:?}",
8961 names
8962 );
8963
8964 assert!(
8965 symbols.len() >= 2,
8966 "expected ≥2 symbols, got {}: {:?}",
8967 symbols.len(),
8968 names
8969 );
8970 }
8971
8972 #[test]
8973 fn tsx_jsx_doesnt_break_parser() {
8974 let provider = TreeSitterProvider::new();
8976 let result = provider.list_symbols(&fixture_path("sample.tsx"));
8977 assert!(
8978 result.is_ok(),
8979 "TSX parsing should succeed: {:?}",
8980 result.err()
8981 );
8982 }
8983
8984 #[test]
8987 fn resolve_symbol_finds_match() {
8988 let provider = TreeSitterProvider::new();
8989 let matches = provider
8990 .resolve_symbol(&fixture_path("sample.ts"), "greet")
8991 .unwrap();
8992 assert_eq!(matches.len(), 1);
8993 assert_eq!(matches[0].symbol.name, "greet");
8994 assert_eq!(matches[0].symbol.kind, SymbolKind::Function);
8995 }
8996
8997 #[test]
8998 fn resolve_symbol_not_found() {
8999 let provider = TreeSitterProvider::new();
9000 let result = provider.resolve_symbol(&fixture_path("sample.ts"), "nonexistent");
9001 assert!(matches!(result, Err(AftError::SymbolNotFound { .. })));
9002 }
9003
9004 #[test]
9005 fn resolve_symbol_follows_reexport_chains() {
9006 let dir = tempfile::tempdir().unwrap();
9007 let config = dir.path().join("config.ts");
9008 let barrel1 = dir.path().join("barrel1.ts");
9009 let barrel2 = dir.path().join("barrel2.ts");
9010 let barrel3 = dir.path().join("barrel3.ts");
9011 let index = dir.path().join("index.ts");
9012
9013 std::fs::write(
9014 &config,
9015 "export class Config {}\nexport default class DefaultConfig {}\n",
9016 )
9017 .unwrap();
9018 std::fs::write(
9019 &barrel1,
9020 "export { Config } from './config';\nexport { default as NamedDefault } from './config';\n",
9021 )
9022 .unwrap();
9023 std::fs::write(
9024 &barrel2,
9025 "export { Config as RenamedConfig } from './barrel1';\n",
9026 )
9027 .unwrap();
9028 std::fs::write(
9029 &barrel3,
9030 "export * from './barrel2';\nexport * from './barrel1';\n",
9031 )
9032 .unwrap();
9033 std::fs::write(
9034 &index,
9035 "export class Config {}\nexport { RenamedConfig as FinalConfig } from './barrel3';\nexport * from './barrel3';\n",
9036 )
9037 .unwrap();
9038
9039 let provider = TreeSitterProvider::new();
9040 let config_canon = std::fs::canonicalize(&config).unwrap();
9041
9042 let direct = provider.resolve_symbol(&barrel1, "Config").unwrap();
9043 assert_eq!(direct.len(), 1);
9044 assert_eq!(direct[0].symbol.name, "Config");
9045 assert_eq!(direct[0].file, config_canon.display().to_string());
9046
9047 let renamed = provider.resolve_symbol(&barrel2, "RenamedConfig").unwrap();
9048 assert_eq!(renamed.len(), 1);
9049 assert_eq!(renamed[0].symbol.name, "Config");
9050 assert_eq!(renamed[0].file, config_canon.display().to_string());
9051
9052 let wildcard_chain = provider.resolve_symbol(&index, "FinalConfig").unwrap();
9053 assert_eq!(wildcard_chain.len(), 1);
9054 assert_eq!(wildcard_chain[0].symbol.name, "Config");
9055 assert_eq!(wildcard_chain[0].file, config_canon.display().to_string());
9056
9057 let wildcard_default = provider.resolve_symbol(&index, "NamedDefault").unwrap();
9058 assert_eq!(wildcard_default.len(), 1);
9059 assert_eq!(wildcard_default[0].symbol.name, "DefaultConfig");
9060 assert_eq!(wildcard_default[0].file, config_canon.display().to_string());
9061
9062 let local = provider.resolve_symbol(&index, "Config").unwrap();
9063 assert_eq!(local.len(), 1);
9064 assert_eq!(local[0].symbol.name, "Config");
9065 assert_eq!(local[0].file, index.display().to_string());
9066 }
9067
9068 #[test]
9071 fn symbol_range_includes_rust_attributes() {
9072 let dir = tempfile::tempdir().unwrap();
9073 let path = dir.path().join("test_attrs.rs");
9074 std::fs::write(
9075 &path,
9076 "/// This is a doc comment\n#[test]\n#[cfg(test)]\nfn my_test_fn() {\n assert!(true);\n}\n",
9077 )
9078 .unwrap();
9079
9080 let provider = TreeSitterProvider::new();
9081 let matches = provider.resolve_symbol(&path, "my_test_fn").unwrap();
9082 assert_eq!(matches.len(), 1);
9083 assert_eq!(
9084 matches[0].symbol.range.start_line, 0,
9085 "symbol range should include preceding /// doc comment, got start_line={}",
9086 matches[0].symbol.range.start_line
9087 );
9088 }
9089
9090 #[test]
9091 fn symbol_range_includes_go_doc_comment() {
9092 let dir = tempfile::tempdir().unwrap();
9093 let path = dir.path().join("test_doc.go");
9094 std::fs::write(
9095 &path,
9096 "package main\n\n// MyFunc does something useful.\n// It has a multi-line doc.\nfunc MyFunc() {\n}\n",
9097 )
9098 .unwrap();
9099
9100 let provider = TreeSitterProvider::new();
9101 let matches = provider.resolve_symbol(&path, "MyFunc").unwrap();
9102 assert_eq!(matches.len(), 1);
9103 assert_eq!(
9104 matches[0].symbol.range.start_line, 2,
9105 "symbol range should include preceding doc comments, got start_line={}",
9106 matches[0].symbol.range.start_line
9107 );
9108 }
9109
9110 #[test]
9111 fn symbol_range_skips_unrelated_comments() {
9112 let dir = tempfile::tempdir().unwrap();
9113 let path = dir.path().join("test_gap.go");
9114 std::fs::write(
9115 &path,
9116 "package main\n\n// This is a standalone comment\n\nfunc Standalone() {\n}\n",
9117 )
9118 .unwrap();
9119
9120 let provider = TreeSitterProvider::new();
9121 let matches = provider.resolve_symbol(&path, "Standalone").unwrap();
9122 assert_eq!(matches.len(), 1);
9123 assert_eq!(
9124 matches[0].symbol.range.start_line, 4,
9125 "symbol range should NOT include comment separated by blank line, got start_line={}",
9126 matches[0].symbol.range.start_line
9127 );
9128 }
9129
9130 #[test]
9131 fn parse_cache_returns_same_tree() {
9132 let mut parser = FileParser::new();
9133 let path = fixture_path("sample.ts");
9134
9135 let (tree1, _) = parser.parse(&path).unwrap();
9136 let tree1_root = tree1.root_node().byte_range();
9137
9138 let (tree2, _) = parser.parse(&path).unwrap();
9139 let tree2_root = tree2.root_node().byte_range();
9140
9141 assert_eq!(tree1_root, tree2_root);
9143 }
9144
9145 #[test]
9146 fn extract_symbols_from_tree_matches_list_symbols() {
9147 let path = fixture_path("sample.rs");
9148 let source = std::fs::read_to_string(&path).unwrap();
9149
9150 let provider = TreeSitterProvider::new();
9151 let listed = provider.list_symbols(&path).unwrap();
9152
9153 let mut parser = FileParser::new();
9154 let (tree, lang) = parser.parse(&path).unwrap();
9155 let extracted = extract_symbols_from_tree(&source, tree, lang).unwrap();
9156
9157 assert_eq!(symbols_as_debug(&extracted), symbols_as_debug(&listed));
9158 }
9159
9160 fn symbols_as_debug(symbols: &[Symbol]) -> Vec<String> {
9161 symbols
9162 .iter()
9163 .map(|symbol| {
9164 format!(
9165 "{}|{:?}|{}:{}-{}:{}|{:?}|{:?}|{}|{:?}",
9166 symbol.name,
9167 symbol.kind,
9168 symbol.range.start_line,
9169 symbol.range.start_col,
9170 symbol.range.end_line,
9171 symbol.range.end_col,
9172 symbol.signature,
9173 symbol.scope_chain,
9174 symbol.exported,
9175 symbol.parent,
9176 )
9177 })
9178 .collect()
9179 }
9180
9181 #[test]
9184 fn py_extracts_all_symbols() {
9185 let provider = TreeSitterProvider::new();
9186 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
9187
9188 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
9189 assert!(
9190 names.contains(&"top_level_function"),
9191 "missing top_level_function: {:?}",
9192 names
9193 );
9194 assert!(names.contains(&"MyClass"), "missing MyClass: {:?}", names);
9195 assert!(
9196 names.contains(&"instance_method"),
9197 "missing method instance_method: {:?}",
9198 names
9199 );
9200 assert!(
9201 names.contains(&"decorated_function"),
9202 "missing decorated_function: {:?}",
9203 names
9204 );
9205
9206 assert!(
9208 symbols.len() >= 4,
9209 "expected ≥4 symbols, got {}: {:?}",
9210 symbols.len(),
9211 names
9212 );
9213 }
9214
9215 #[test]
9216 fn py_symbol_kinds_correct() {
9217 let provider = TreeSitterProvider::new();
9218 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
9219
9220 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
9221
9222 assert_eq!(find("top_level_function").kind, SymbolKind::Function);
9223 assert_eq!(find("MyClass").kind, SymbolKind::Class);
9224 assert_eq!(find("instance_method").kind, SymbolKind::Method);
9225 assert_eq!(find("decorated_function").kind, SymbolKind::Function);
9226 assert_eq!(find("OuterClass").kind, SymbolKind::Class);
9227 assert_eq!(find("InnerClass").kind, SymbolKind::Class);
9228 assert_eq!(find("inner_method").kind, SymbolKind::Method);
9229 assert_eq!(find("outer_method").kind, SymbolKind::Method);
9230 }
9231
9232 #[test]
9233 fn py_method_scope_chain() {
9234 let provider = TreeSitterProvider::new();
9235 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
9236
9237 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
9238
9239 assert_eq!(
9241 find("instance_method").scope_chain,
9242 vec!["MyClass"],
9243 "instance_method should have MyClass in scope chain"
9244 );
9245 assert_eq!(find("instance_method").parent.as_deref(), Some("MyClass"));
9246
9247 assert_eq!(
9249 find("inner_method").scope_chain,
9250 vec!["OuterClass", "InnerClass"],
9251 "inner_method should have nested scope chain"
9252 );
9253
9254 assert_eq!(
9256 find("InnerClass").scope_chain,
9257 vec!["OuterClass"],
9258 "InnerClass should have OuterClass in scope"
9259 );
9260
9261 assert!(
9263 find("top_level_function").scope_chain.is_empty(),
9264 "top-level function should have empty scope chain"
9265 );
9266 }
9267
9268 #[test]
9269 fn py_decorated_function_signature() {
9270 let provider = TreeSitterProvider::new();
9271 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
9272
9273 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
9274
9275 let sig = find("decorated_function").signature.as_ref().unwrap();
9276 assert!(
9277 sig.contains("@staticmethod"),
9278 "decorated function signature should include decorator: {}",
9279 sig
9280 );
9281 assert!(
9282 sig.contains("def decorated_function"),
9283 "signature should include function def: {}",
9284 sig
9285 );
9286 }
9287
9288 #[test]
9289 fn py_ranges_valid() {
9290 let provider = TreeSitterProvider::new();
9291 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
9292
9293 for s in &symbols {
9294 assert!(
9295 s.range.end_line >= s.range.start_line,
9296 "symbol {} has invalid range: {:?}",
9297 s.name,
9298 s.range
9299 );
9300 }
9301 }
9302
9303 #[test]
9306 fn rs_extracts_all_symbols() {
9307 let provider = TreeSitterProvider::new();
9308 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
9309
9310 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
9311 assert!(
9312 names.contains(&"public_function"),
9313 "missing public_function: {:?}",
9314 names
9315 );
9316 assert!(
9317 names.contains(&"private_function"),
9318 "missing private_function: {:?}",
9319 names
9320 );
9321 assert!(names.contains(&"MyStruct"), "missing MyStruct: {:?}", names);
9322 assert!(names.contains(&"Color"), "missing enum Color: {:?}", names);
9323 assert!(
9324 names.contains(&"Drawable"),
9325 "missing trait Drawable: {:?}",
9326 names
9327 );
9328 assert!(
9330 names.contains(&"new"),
9331 "missing impl method new: {:?}",
9332 names
9333 );
9334
9335 assert!(
9337 symbols.len() >= 6,
9338 "expected ≥6 symbols, got {}: {:?}",
9339 symbols.len(),
9340 names
9341 );
9342 }
9343
9344 #[test]
9345 fn rs_symbol_kinds_correct() {
9346 let provider = TreeSitterProvider::new();
9347 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
9348
9349 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
9350
9351 assert_eq!(find("public_function").kind, SymbolKind::Function);
9352 assert_eq!(find("private_function").kind, SymbolKind::Function);
9353 assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
9354 assert_eq!(find("Color").kind, SymbolKind::Enum);
9355 assert_eq!(find("Drawable").kind, SymbolKind::Interface); assert_eq!(find("new").kind, SymbolKind::Method);
9357 }
9358
9359 #[test]
9360 fn rs_pub_export_detection() {
9361 let provider = TreeSitterProvider::new();
9362 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
9363
9364 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
9365
9366 assert!(
9367 find("public_function").exported,
9368 "pub fn should be exported"
9369 );
9370 assert!(
9371 !find("private_function").exported,
9372 "non-pub fn should not be exported"
9373 );
9374 assert!(find("MyStruct").exported, "pub struct should be exported");
9375 assert!(find("Color").exported, "pub enum should be exported");
9376 assert!(find("Drawable").exported, "pub trait should be exported");
9377 assert!(
9378 find("new").exported,
9379 "pub fn inside impl should be exported"
9380 );
9381 assert!(
9382 !find("helper").exported,
9383 "non-pub fn inside impl should not be exported"
9384 );
9385 }
9386
9387 #[test]
9388 fn rs_impl_method_scope_chain() {
9389 let provider = TreeSitterProvider::new();
9390 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
9391
9392 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
9393
9394 assert_eq!(
9396 find("new").scope_chain,
9397 vec!["MyStruct"],
9398 "impl method should have type in scope chain"
9399 );
9400 assert_eq!(find("new").parent.as_deref(), Some("MyStruct"));
9401
9402 assert!(
9404 find("public_function").scope_chain.is_empty(),
9405 "free function should have empty scope chain"
9406 );
9407 }
9408
9409 #[test]
9410 fn rs_trait_impl_scope_chain() {
9411 let provider = TreeSitterProvider::new();
9412 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
9413
9414 let draw = symbols.iter().find(|s| s.name == "draw").unwrap();
9416 assert_eq!(
9417 draw.scope_chain,
9418 vec!["Drawable for MyStruct"],
9419 "trait impl method should have 'Trait for Type' scope"
9420 );
9421 assert_eq!(draw.parent.as_deref(), Some("MyStruct"));
9422 }
9423
9424 #[test]
9425 fn rs_ranges_valid() {
9426 let provider = TreeSitterProvider::new();
9427 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
9428
9429 for s in &symbols {
9430 assert!(
9431 s.range.end_line >= s.range.start_line,
9432 "symbol {} has invalid range: {:?}",
9433 s.name,
9434 s.range
9435 );
9436 }
9437 }
9438
9439 #[test]
9442 fn go_extracts_all_symbols() {
9443 let provider = TreeSitterProvider::new();
9444 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
9445
9446 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
9447 assert!(
9448 names.contains(&"ExportedFunction"),
9449 "missing ExportedFunction: {:?}",
9450 names
9451 );
9452 assert!(
9453 names.contains(&"unexportedFunction"),
9454 "missing unexportedFunction: {:?}",
9455 names
9456 );
9457 assert!(
9458 names.contains(&"MyStruct"),
9459 "missing struct MyStruct: {:?}",
9460 names
9461 );
9462 assert!(
9463 names.contains(&"Reader"),
9464 "missing interface Reader: {:?}",
9465 names
9466 );
9467 assert!(
9469 names.contains(&"String"),
9470 "missing receiver method String: {:?}",
9471 names
9472 );
9473
9474 assert!(
9476 symbols.len() >= 4,
9477 "expected ≥4 symbols, got {}: {:?}",
9478 symbols.len(),
9479 names
9480 );
9481 }
9482
9483 #[test]
9484 fn go_symbol_kinds_correct() {
9485 let provider = TreeSitterProvider::new();
9486 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
9487
9488 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
9489
9490 assert_eq!(find("ExportedFunction").kind, SymbolKind::Function);
9491 assert_eq!(find("unexportedFunction").kind, SymbolKind::Function);
9492 assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
9493 assert_eq!(find("Reader").kind, SymbolKind::Interface);
9494 assert_eq!(find("String").kind, SymbolKind::Method);
9495 assert_eq!(find("helper").kind, SymbolKind::Method);
9496 }
9497
9498 #[test]
9499 fn go_uppercase_export_detection() {
9500 let provider = TreeSitterProvider::new();
9501 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
9502
9503 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
9504
9505 assert!(
9506 find("ExportedFunction").exported,
9507 "ExportedFunction (uppercase) should be exported"
9508 );
9509 assert!(
9510 !find("unexportedFunction").exported,
9511 "unexportedFunction (lowercase) should not be exported"
9512 );
9513 assert!(
9514 find("MyStruct").exported,
9515 "MyStruct (uppercase) should be exported"
9516 );
9517 assert!(
9518 find("Reader").exported,
9519 "Reader (uppercase) should be exported"
9520 );
9521 assert!(
9522 find("String").exported,
9523 "String method (uppercase) should be exported"
9524 );
9525 assert!(
9526 !find("helper").exported,
9527 "helper method (lowercase) should not be exported"
9528 );
9529 }
9530
9531 #[test]
9532 fn go_receiver_method_scope_chain() {
9533 let provider = TreeSitterProvider::new();
9534 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
9535
9536 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
9537
9538 assert_eq!(
9540 find("String").scope_chain,
9541 vec!["MyStruct"],
9542 "receiver method should have type in scope chain"
9543 );
9544 assert_eq!(find("String").parent.as_deref(), Some("MyStruct"));
9545
9546 assert!(
9548 find("ExportedFunction").scope_chain.is_empty(),
9549 "regular function should have empty scope chain"
9550 );
9551 }
9552
9553 #[test]
9554 fn go_ranges_valid() {
9555 let provider = TreeSitterProvider::new();
9556 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
9557
9558 for s in &symbols {
9559 assert!(
9560 s.range.end_line >= s.range.start_line,
9561 "symbol {} has invalid range: {:?}",
9562 s.name,
9563 s.range
9564 );
9565 }
9566 }
9567
9568 #[test]
9571 fn cross_language_all_six_produce_symbols() {
9572 let provider = TreeSitterProvider::new();
9573
9574 let fixtures = [
9575 ("sample.ts", "TypeScript"),
9576 ("sample.tsx", "TSX"),
9577 ("sample.js", "JavaScript"),
9578 ("sample.py", "Python"),
9579 ("sample.rs", "Rust"),
9580 ("sample.go", "Go"),
9581 ];
9582
9583 for (fixture, lang) in &fixtures {
9584 let symbols = provider
9585 .list_symbols(&fixture_path(fixture))
9586 .unwrap_or_else(|e| panic!("{} ({}) failed: {:?}", lang, fixture, e));
9587 assert!(
9588 symbols.len() >= 2,
9589 "{} should produce ≥2 symbols, got {}: {:?}",
9590 lang,
9591 symbols.len(),
9592 symbols.iter().map(|s| &s.name).collect::<Vec<_>>()
9593 );
9594 }
9595 }
9596
9597 fn cached_freshness_fixture(
9600 content: &str,
9601 ) -> (tempfile::TempDir, PathBuf, SystemTime, u64, blake3::Hash) {
9602 let dir = tempfile::tempdir().unwrap();
9603 let file = dir.path().join("test.rs");
9604 std::fs::write(&file, content).unwrap();
9605 filetime::set_file_mtime(&file, filetime::FileTime::from_unix_time(1, 0)).unwrap();
9606 let metadata = std::fs::metadata(&file).unwrap();
9607 let mtime = metadata.modified().unwrap();
9608 let size = metadata.len();
9609 let hash = cache_freshness::hash_bytes(content.as_bytes());
9610 (dir, file, mtime, size, hash)
9611 }
9612
9613 #[cfg(debug_assertions)]
9614 #[test]
9615 fn cached_freshness_unchanged_metadata_skips_hashing() {
9616 let (_dir, file, mtime, size, hash) = cached_freshness_fixture("pub fn hello() {}\n");
9617
9618 cache_freshness::reset_hash_file_if_small_count_for_debug();
9619 assert!(cached_file_is_fresh(&file, mtime, size, hash, mtime));
9620 assert_eq!(cache_freshness::hash_file_if_small_count_for_debug(), 0);
9621 }
9622
9623 #[cfg(debug_assertions)]
9624 #[test]
9625 fn cached_freshness_changed_mtime_with_identical_content_hashes_fresh() {
9626 let (_dir, file, mtime, size, hash) = cached_freshness_fixture("pub fn hello() {}\n");
9627 filetime::set_file_mtime(&file, filetime::FileTime::from_unix_time(2, 0)).unwrap();
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_mtime_and_same_size_content_hashes_stale() {
9637 let (_dir, file, mtime, size, hash) = cached_freshness_fixture("pub fn one() {}\n");
9638 std::fs::write(&file, "pub fn two() {}\n").unwrap();
9639 filetime::set_file_mtime(&file, filetime::FileTime::from_unix_time(2, 0)).unwrap();
9640 assert_eq!(std::fs::metadata(&file).unwrap().len(), size);
9641
9642 cache_freshness::reset_hash_file_if_small_count_for_debug();
9643 assert!(!cached_file_is_fresh(&file, mtime, size, hash, mtime));
9644 assert_eq!(cache_freshness::hash_file_if_small_count_for_debug(), 1);
9645 }
9646
9647 #[cfg(debug_assertions)]
9648 #[test]
9649 fn cached_freshness_changed_size_skips_hashing() {
9650 let (_dir, file, mtime, size, hash) = cached_freshness_fixture("pub fn hello() {}\n");
9651 std::fs::write(&file, "pub fn hello() {}\n// longer\n").unwrap();
9652
9653 cache_freshness::reset_hash_file_if_small_count_for_debug();
9654 assert!(!cached_file_is_fresh(&file, mtime, size, hash, mtime));
9655 assert_eq!(cache_freshness::hash_file_if_small_count_for_debug(), 0);
9656 }
9657
9658 #[test]
9659 fn symbol_cache_returns_cached_results_on_second_call() {
9660 let dir = tempfile::tempdir().unwrap();
9661 let file = dir.path().join("test.rs");
9662 std::fs::write(&file, "pub fn hello() {}\npub fn world() {}").unwrap();
9663
9664 let mut parser = FileParser::new();
9665
9666 let symbols1 = parser.extract_symbols(&file).unwrap();
9667 assert_eq!(symbols1.len(), 2);
9668
9669 let symbols2 = parser.extract_symbols(&file).unwrap();
9671 assert_eq!(symbols2.len(), 2);
9672 assert_eq!(symbols1[0].name, symbols2[0].name);
9673
9674 assert!(parser.symbol_cache.read().unwrap().contains_key(&file));
9676 }
9677
9678 #[test]
9679 fn symbol_cache_invalidates_on_file_change() {
9680 let dir = tempfile::tempdir().unwrap();
9681 let file = dir.path().join("test.rs");
9682 std::fs::write(&file, "pub fn hello() {}").unwrap();
9683
9684 let mut parser = FileParser::new();
9685
9686 let symbols1 = parser.extract_symbols(&file).unwrap();
9687 assert_eq!(symbols1.len(), 1);
9688 assert_eq!(symbols1[0].name, "hello");
9689
9690 std::thread::sleep(std::time::Duration::from_millis(50));
9692
9693 std::fs::write(&file, "pub fn hello() {}\npub fn goodbye() {}").unwrap();
9695
9696 let symbols2 = parser.extract_symbols(&file).unwrap();
9698 assert_eq!(symbols2.len(), 2);
9699 assert!(symbols2.iter().any(|s| s.name == "goodbye"));
9700 }
9701
9702 #[test]
9703 fn symbol_cache_invalidate_method_clears_entry() {
9704 let dir = tempfile::tempdir().unwrap();
9705 let file = dir.path().join("test.rs");
9706 std::fs::write(&file, "pub fn hello() {}").unwrap();
9707
9708 let mut parser = FileParser::new();
9709 parser.extract_symbols(&file).unwrap();
9710 assert!(parser.symbol_cache.read().unwrap().contains_key(&file));
9711
9712 parser.invalidate_symbols(&file);
9713 assert!(!parser.symbol_cache.read().unwrap().contains_key(&file));
9714 assert!(!parser.cache.contains_key(&file));
9716 }
9717
9718 #[test]
9719 fn symbol_cache_works_for_multiple_languages() {
9720 let dir = tempfile::tempdir().unwrap();
9721 let rs_file = dir.path().join("lib.rs");
9722 let ts_file = dir.path().join("app.ts");
9723 let py_file = dir.path().join("main.py");
9724
9725 std::fs::write(&rs_file, "pub fn rust_fn() {}").unwrap();
9726 std::fs::write(&ts_file, "export function tsFn() {}").unwrap();
9727 std::fs::write(&py_file, "def py_fn():\n pass").unwrap();
9728
9729 let mut parser = FileParser::new();
9730
9731 let rs_syms = parser.extract_symbols(&rs_file).unwrap();
9732 let ts_syms = parser.extract_symbols(&ts_file).unwrap();
9733 let py_syms = parser.extract_symbols(&py_file).unwrap();
9734
9735 assert!(rs_syms.iter().any(|s| s.name == "rust_fn"));
9736 assert!(ts_syms.iter().any(|s| s.name == "tsFn"));
9737 assert!(py_syms.iter().any(|s| s.name == "py_fn"));
9738
9739 assert_eq!(parser.symbol_cache.read().unwrap().len(), 3);
9741
9742 let rs_syms2 = parser.extract_symbols(&rs_file).unwrap();
9744 assert_eq!(rs_syms.len(), rs_syms2.len());
9745 }
9746
9747 #[test]
9748 fn extract_json_symbols_top_level_keys() {
9749 let dir = tempfile::tempdir().unwrap();
9750 let file = dir.path().join("package.json");
9751 std::fs::write(&file, r#"{"name": "x", "version": "1"}"#).unwrap();
9752
9753 let mut parser = FileParser::new();
9754 let symbols = parser.extract_symbols(&file).unwrap();
9755
9756 assert_eq!(symbols.len(), 2);
9757 assert!(symbols
9758 .iter()
9759 .any(|s| s.name == "name" && s.kind == SymbolKind::Variable));
9760 assert!(symbols
9761 .iter()
9762 .any(|s| s.name == "version" && s.kind == SymbolKind::Variable));
9763 }
9764
9765 #[test]
9766 fn extract_json_symbols_root_array() {
9767 let dir = tempfile::tempdir().unwrap();
9768 let file = dir.path().join("array.json");
9769 std::fs::write(&file, "[1,2,3]").unwrap();
9770
9771 let mut parser = FileParser::new();
9772 let symbols = parser.extract_symbols(&file).unwrap();
9773
9774 assert_eq!(symbols.len(), 0);
9775 }
9776
9777 #[test]
9778 fn extract_json_symbols_no_recursion_into_nested() {
9779 let dir = tempfile::tempdir().unwrap();
9780 let file = dir.path().join("nested.json");
9781 std::fs::write(&file, r#"{"scripts": {"build": "tsc"}}"#).unwrap();
9782
9783 let mut parser = FileParser::new();
9784 let symbols = parser.extract_symbols(&file).unwrap();
9785
9786 assert_eq!(symbols.len(), 1);
9787 assert_eq!(symbols[0].name, "scripts");
9788 assert!(!symbols.iter().any(|s| s.name == "build"));
9789 }
9790
9791 #[test]
9792 fn extract_scala_symbols_object_and_method() {
9793 let dir = tempfile::tempdir().unwrap();
9794 let file = dir.path().join("Greeter.scala");
9795 std::fs::write(
9796 &file,
9797 "object Greeter {\n def hello(name: String): String = s\"hi $name\"\n}",
9798 )
9799 .unwrap();
9800
9801 let mut parser = FileParser::new();
9802 let symbols = parser.extract_symbols(&file).unwrap();
9803
9804 assert!(symbols
9805 .iter()
9806 .any(|s| s.name == "Greeter" && s.kind == SymbolKind::Class));
9807 assert!(symbols.iter().any(|s| s.name == "hello"
9808 && s.kind == SymbolKind::Method
9809 && s.scope_chain == vec!["Greeter".to_string()]));
9810 }
9811
9812 #[test]
9813 fn extract_scala_symbols_class_and_trait() {
9814 let dir = tempfile::tempdir().unwrap();
9815 let file = dir.path().join("Types.scala");
9816 std::fs::write(&file, "class Foo\ntrait Bar").unwrap();
9817
9818 let mut parser = FileParser::new();
9819 let symbols = parser.extract_symbols(&file).unwrap();
9820
9821 assert!(symbols
9822 .iter()
9823 .any(|s| s.name == "Foo" && s.kind == SymbolKind::Class));
9824 assert!(symbols
9825 .iter()
9826 .any(|s| s.name == "Bar" && s.kind == SymbolKind::Interface));
9827 }
9828
9829 #[test]
9830 fn extract_yaml_symbols_k8s_resource() {
9831 let dir = tempfile::tempdir().unwrap();
9832 let file = dir.path().join("deployment.yaml");
9833 std::fs::write(
9834 &file,
9835 r#"apiVersion: apps/v1
9836kind: Deployment
9837metadata:
9838 name: nginx
9839 namespace: web
9840"#,
9841 )
9842 .unwrap();
9843
9844 let mut parser = FileParser::new();
9845 let symbols = parser.extract_symbols(&file).unwrap();
9846
9847 assert_eq!(symbols.len(), 1, "Expected 1 symbol for K8s Deployment");
9848 let sym = &symbols[0];
9849 assert_eq!(sym.name, "web/Deployment/nginx");
9850 assert_eq!(sym.kind, SymbolKind::Class);
9851 assert!(sym.exported);
9852 }
9853
9854 #[test]
9855 fn extract_yaml_symbols_k8s_no_namespace() {
9856 let dir = tempfile::tempdir().unwrap();
9857 let file = dir.path().join("service.yaml");
9858 std::fs::write(
9859 &file,
9860 r#"apiVersion: v1
9861kind: Service
9862metadata:
9863 name: nginx-svc
9864"#,
9865 )
9866 .unwrap();
9867
9868 let mut parser = FileParser::new();
9869 let symbols = parser.extract_symbols(&file).unwrap();
9870
9871 assert_eq!(symbols.len(), 1, "Expected 1 symbol for K8s Service");
9872 let sym = &symbols[0];
9873 assert_eq!(sym.name, "Service/nginx-svc");
9874 assert_eq!(sym.kind, SymbolKind::Class);
9875 }
9876
9877 #[test]
9878 fn extract_yaml_symbols_multidoc() {
9879 let dir = tempfile::tempdir().unwrap();
9880 let file = dir.path().join("multidoc.yaml");
9881 std::fs::write(
9882 &file,
9883 r#"apiVersion: apps/v1
9884kind: Deployment
9885metadata:
9886 name: a
9887---
9888apiVersion: v1
9889kind: Service
9890metadata:
9891 name: b
9892"#,
9893 )
9894 .unwrap();
9895
9896 let mut parser = FileParser::new();
9897 let symbols = parser.extract_symbols(&file).unwrap();
9898
9899 assert_eq!(symbols.len(), 2, "Expected 2 symbols for multi-doc YAML");
9900 assert!(symbols.iter().any(|s| s.name == "Deployment/a"));
9901 assert!(symbols.iter().any(|s| s.name == "Service/b"));
9902 }
9903
9904 #[test]
9905 fn extract_yaml_symbols_generic_fallback() {
9906 let dir = tempfile::tempdir().unwrap();
9907 let file = dir.path().join("compose.yaml");
9908 std::fs::write(
9909 &file,
9910 r#"version: "3"
9911services:
9912 web: {}
9913volumes:
9914 data: {}
9915"#,
9916 )
9917 .unwrap();
9918
9919 let mut parser = FileParser::new();
9920 let symbols = parser.extract_symbols(&file).unwrap();
9921
9922 assert_eq!(symbols.len(), 3, "Expected 3 symbols for generic YAML");
9924 assert!(symbols
9925 .iter()
9926 .any(|s| s.name == "version" && s.kind == SymbolKind::Variable));
9927 assert!(symbols
9928 .iter()
9929 .any(|s| s.name == "services" && s.kind == SymbolKind::Variable));
9930 assert!(symbols
9931 .iter()
9932 .any(|s| s.name == "volumes" && s.kind == SymbolKind::Variable));
9933 }
9934
9935 #[test]
9936 fn extract_yaml_symbols_empty() {
9937 let dir = tempfile::tempdir().unwrap();
9938 let file = dir.path().join("empty.yaml");
9939 std::fs::write(&file, "").unwrap();
9940
9941 let mut parser = FileParser::new();
9942 let symbols = parser.extract_symbols(&file).unwrap();
9943
9944 assert_eq!(symbols.len(), 0, "Expected 0 symbols for empty YAML");
9945 }
9946
9947 #[test]
9948 fn extract_yaml_symbols_resource_limits() {
9949 let dir = tempfile::tempdir().unwrap();
9950 let file = dir.path().join("deployment.yaml");
9951 std::fs::write(
9952 &file,
9953 r#"apiVersion: apps/v1
9954kind: Deployment
9955metadata:
9956 name: app
9957spec:
9958 template:
9959 spec:
9960 containers:
9961 - name: main
9962 image: myapp:1.0
9963 resources:
9964 limits:
9965 cpu: "2"
9966 memory: 1Gi
9967 requests:
9968 cpu: "1"
9969 memory: 512Mi
9970"#,
9971 )
9972 .unwrap();
9973
9974 let mut parser = FileParser::new();
9975 let symbols = parser.extract_symbols(&file).unwrap();
9976
9977 assert_eq!(symbols.len(), 1, "Expected 1 symbol for Deployment");
9978 let sym = &symbols[0];
9979 assert_eq!(sym.name, "Deployment/app");
9980 let sig = sym.signature.as_ref().unwrap();
9981 assert!(sig.contains("cpu="), "Signature should contain cpu= field");
9982 assert!(
9983 sig.contains("memory="),
9984 "Signature should contain memory= field"
9985 );
9986 }
9987
9988 #[test]
9989 fn extract_yaml_symbols_env_names() {
9990 let dir = tempfile::tempdir().unwrap();
9991 let file = dir.path().join("deployment.yaml");
9992 std::fs::write(
9993 &file,
9994 r#"apiVersion: apps/v1
9995kind: Deployment
9996metadata:
9997 name: app
9998spec:
9999 template:
10000 spec:
10001 containers:
10002 - name: main
10003 image: myapp:1.0
10004 env:
10005 - name: FOO
10006 value: "bar"
10007 - name: BAR
10008 value: "baz"
10009"#,
10010 )
10011 .unwrap();
10012
10013 let mut parser = FileParser::new();
10014 let symbols = parser.extract_symbols(&file).unwrap();
10015
10016 assert_eq!(symbols.len(), 1, "Expected 1 symbol for Deployment");
10017 let sym = &symbols[0];
10018 let sig = sym.signature.as_ref().unwrap();
10019 assert!(
10020 sig.contains("env=FOO,BAR"),
10021 "Signature should contain env=FOO,BAR"
10022 );
10023 }
10024
10025 #[test]
10026 fn extract_yaml_symbols_rbac_rules() {
10027 let dir = tempfile::tempdir().unwrap();
10028 let file = dir.path().join("role.yaml");
10029 std::fs::write(
10030 &file,
10031 r#"apiVersion: rbac.authorization.k8s.io/v1
10032kind: Role
10033metadata:
10034 name: reader
10035rules:
10036- apiGroups: [""]
10037 resources: [pods, services]
10038 verbs: [get, list, watch]
10039"#,
10040 )
10041 .unwrap();
10042
10043 let mut parser = FileParser::new();
10044 let symbols = parser.extract_symbols(&file).unwrap();
10045
10046 assert_eq!(symbols.len(), 1, "Expected 1 symbol for Role");
10047 let sym = &symbols[0];
10048 let sig = sym.signature.as_ref().unwrap();
10049 assert!(
10050 sig.contains("verbs=get,list,watch"),
10051 "Signature should contain verbs=get,list,watch"
10052 );
10053 assert!(
10054 sig.contains("resources=pods,services"),
10055 "Signature should contain resources=pods,services"
10056 );
10057 }
10058
10059 #[test]
10060 fn extract_yaml_symbols_argo_workflow() {
10061 let dir = tempfile::tempdir().unwrap();
10062 let file = dir.path().join("workflow.yaml");
10063 std::fs::write(
10064 &file,
10065 r#"apiVersion: argoproj.io/v1alpha1
10066kind: Workflow
10067metadata:
10068 name: hello-world
10069spec:
10070 entrypoint: main
10071 templates:
10072 - name: main
10073 container:
10074 image: alpine:3.18
10075 command: [echo]
10076 args: ["hello"]
10077 - name: print
10078 container:
10079 image: alpine:3.18
10080 command: [echo]
10081 args: ["world"]
10082"#,
10083 )
10084 .unwrap();
10085
10086 let mut parser = FileParser::new();
10087 let symbols = parser.extract_symbols(&file).unwrap();
10088
10089 assert_eq!(symbols.len(), 1, "Expected 1 symbol for Workflow");
10090 let sym = &symbols[0];
10091 assert!(
10092 sym.name.contains("Workflow"),
10093 "Symbol name should contain Workflow"
10094 );
10095 let sig = sym.signature.as_ref().unwrap();
10096 assert!(
10097 sig.contains("entrypoint=main"),
10098 "Signature should contain entrypoint=main"
10099 );
10100 assert!(
10101 sig.contains("templates=main,print"),
10102 "Signature should contain templates=main,print"
10103 );
10104 assert!(
10105 sig.contains("image=alpine:3.18"),
10106 "Signature should contain image=alpine:3.18"
10107 );
10108 assert!(
10109 sig.contains("command=echo"),
10110 "Signature should contain command=echo"
10111 );
10112 }
10113
10114 #[test]
10115 fn extract_yaml_symbols_generatename_fallback() {
10116 let dir = tempfile::tempdir().unwrap();
10117 let file = dir.path().join("workflow.yaml");
10118 std::fs::write(
10119 &file,
10120 r#"apiVersion: argoproj.io/v1alpha1
10121kind: Workflow
10122metadata:
10123 generateName: hello-
10124spec:
10125 entrypoint: main
10126 templates:
10127 - name: main
10128 container:
10129 image: alpine:3.18
10130"#,
10131 )
10132 .unwrap();
10133
10134 let mut parser = FileParser::new();
10135 let symbols = parser.extract_symbols(&file).unwrap();
10136
10137 assert_eq!(symbols.len(), 1, "Expected 1 symbol for Workflow");
10138 let sym = &symbols[0];
10139 assert!(
10140 sym.name.contains("hello-"),
10141 "Symbol name should contain generateName fallback 'hello-'"
10142 );
10143 }
10144
10145 #[test]
10146 fn detect_r_extensions_are_case_sensitive() {
10147 assert_eq!(detect_language(Path::new("analysis.R")), Some(LangId::R));
10148 assert_eq!(detect_language(Path::new("script.r")), Some(LangId::R));
10149 }
10150
10151 #[test]
10152 fn extract_r_symbols_test() {
10153 let dir = tempfile::tempdir().unwrap();
10154 let file = dir.path().join("analysis.R");
10155 std::fs::write(
10156 &file,
10157 r#"
10158# summary function
10159summarise <- function(data, column) {
10160 total <- sum(data[[column]])
10161 total
10162}
10163
10164normalise = function(x) {
10165 x / max(x)
10166}
10167
10168function(y) {
10169 y + 1
10170} -> transform_values
10171
10172threshold <- 10
10173"done" -> status
10174
10175outer <- function(values) {
10176 local_value <- 1
10177 local_value
10178}
10179"#,
10180 )
10181 .unwrap();
10182
10183 let mut parser = FileParser::new();
10184 let symbols = parser.extract_symbols(&file).unwrap();
10185
10186 let get = |name: &str| {
10187 symbols
10188 .iter()
10189 .find(|symbol| symbol.name == name)
10190 .unwrap_or_else(|| panic!("missing {name}; got {symbols:?}"))
10191 };
10192
10193 assert_eq!(get("summarise").kind, SymbolKind::Function);
10194 assert_eq!(get("normalise").kind, SymbolKind::Function);
10195 assert_eq!(get("transform_values").kind, SymbolKind::Function);
10196 assert!(
10197 symbols
10198 .iter()
10199 .all(|symbol| !symbol.name.starts_with("function(")),
10200 "rightward function assignments should use the RHS identifier: {symbols:?}"
10201 );
10202 assert_eq!(get("outer").kind, SymbolKind::Function);
10203 assert_eq!(get("threshold").kind, SymbolKind::Variable);
10204 assert_eq!(get("status").kind, SymbolKind::Variable);
10205 assert!(
10206 symbols.iter().all(|symbol| symbol.name != "local_value"),
10207 "nested assignments should not surface as top-level variables: {symbols:?}"
10208 );
10209 }
10210
10211 #[test]
10212 fn extract_pascal_symbols_test() {
10213 let dir = tempfile::tempdir().unwrap();
10214 let file = dir.path().join("MyUnit.pas");
10215 std::fs::write(
10216 &file,
10217 r#"
10218unit MyUnit;
10219
10220interface
10221
10222uses SysUtils;
10223
10224type
10225 TMyClass = class
10226 private
10227 FValue: Integer;
10228 public
10229 constructor Create;
10230 procedure DoSomething; virtual;
10231 end;
10232
10233 TMyRecord = record
10234 X, Y: Integer;
10235 end;
10236
10237 IMyInterface = interface
10238 procedure DoSomethingElse;
10239 end;
10240
10241 TMyEnum = (Red, Green, Blue);
10242
10243const
10244 UNIT_CONST = 42;
10245
10246var
10247 UnitVar: string;
10248
10249implementation
10250
10251constructor TMyClass.Create;
10252begin
10253 FValue := 0;
10254end;
10255
10256procedure TMyClass.DoSomething;
10257begin
10258end;
10259
10260procedure StandaloneProc;
10261begin
10262end;
10263
10264end.
10265"#,
10266 )
10267 .unwrap();
10268
10269 let mut parser = FileParser::new();
10270 let symbols = parser.extract_symbols(&file).unwrap();
10271
10272 let get = |name: &str| {
10273 symbols
10274 .iter()
10275 .find(|symbol| symbol.name == name)
10276 .unwrap_or_else(|| panic!("missing {name}; got {symbols:?}"))
10277 };
10278
10279 assert_eq!(get("MyUnit").kind, SymbolKind::Class);
10280 assert_eq!(get("TMyClass").kind, SymbolKind::Class);
10281 assert_eq!(get("TMyRecord").kind, SymbolKind::Struct);
10282 assert_eq!(get("IMyInterface").kind, SymbolKind::Interface);
10283 assert_eq!(get("TMyEnum").kind, SymbolKind::Enum);
10284 assert_eq!(get("UNIT_CONST").kind, SymbolKind::Variable);
10285 assert_eq!(get("UnitVar").kind, SymbolKind::Variable);
10286 assert_eq!(get("StandaloneProc").kind, SymbolKind::Function);
10287
10288 let create_methods: Vec<&Symbol> = symbols.iter().filter(|s| s.name == "Create").collect();
10290 assert_eq!(create_methods.len(), 2); for m in create_methods {
10292 assert_eq!(m.kind, SymbolKind::Method);
10293 assert_eq!(m.scope_chain, vec!["TMyClass".to_string()]);
10294 }
10295
10296 let do_something_methods: Vec<&Symbol> =
10297 symbols.iter().filter(|s| s.name == "DoSomething").collect();
10298 assert_eq!(do_something_methods.len(), 2); for m in do_something_methods {
10300 assert_eq!(m.kind, SymbolKind::Method);
10301 assert_eq!(m.scope_chain, vec!["TMyClass".to_string()]);
10302 }
10303 }
10304
10305 #[test]
10306 fn groovy_fixture_extracts_types_methods_and_properties() {
10307 let provider = TreeSitterProvider::new();
10308 let symbols = provider
10309 .list_symbols(&fixture_path("sample.groovy"))
10310 .unwrap();
10311
10312 let get = |name: &str| {
10313 symbols
10314 .iter()
10315 .find(|symbol| symbol.name == name)
10316 .unwrap_or_else(|| panic!("missing {name}; got {symbols:?}"))
10317 };
10318
10319 assert_eq!(get("GreeterSupport").kind, SymbolKind::Interface);
10320 assert_eq!(get("Named").kind, SymbolKind::Interface);
10321 assert_eq!(get("BuildStatus").kind, SymbolKind::Enum);
10322 assert_eq!(get("BuildLogic").kind, SymbolKind::Class);
10323 assert_eq!(get("action").kind, SymbolKind::Variable);
10324 assert_eq!(get("action").scope_chain, vec!["BuildLogic".to_string()]);
10325 assert_eq!(get("greet").kind, SymbolKind::Method);
10326 assert_eq!(get("greet").scope_chain, vec!["BuildLogic".to_string()]);
10327 assert_eq!(get("topLevelHelper").kind, SymbolKind::Function);
10328 assert!(get("topLevelHelper").scope_chain.is_empty());
10329
10330 assert_eq!(get("BuildLogic").range.start_line, 12);
10331 assert_eq!(get("BuildLogic").range.end_line, 23);
10332 assert_eq!(get("action").range.start_line, 14);
10333 assert_eq!(get("action").range.end_line, 14);
10334 assert_eq!(get("greet").range.start_line, 16);
10335 assert_eq!(get("greet").range.end_line, 18);
10336 assert_eq!(get("topLevelHelper").range.start_line, 25);
10337 assert_eq!(get("topLevelHelper").range.end_line, 27);
10338 }
10339
10340 #[test]
10341 fn groovy_gradle_fixture_extracts_task_declarations() {
10342 let provider = TreeSitterProvider::new();
10343 let symbols = provider
10344 .list_symbols(&fixture_path("build.gradle"))
10345 .unwrap();
10346
10347 let get = |name: &str| {
10348 symbols
10349 .iter()
10350 .find(|symbol| symbol.name == name)
10351 .unwrap_or_else(|| panic!("missing {name}; got {symbols:?}"))
10352 };
10353
10354 assert_eq!(get("smokeTest").kind, SymbolKind::Function);
10355 assert_eq!(get("smokeTest").range.start_line, 8);
10356 assert_eq!(get("smokeTest").range.end_line, 12);
10357 assert_eq!(get("ciCheck").kind, SymbolKind::Function);
10358 assert_eq!(get("ciCheck").range.start_line, 14);
10359 assert_eq!(get("ciCheck").range.end_line, 18);
10360 }
10361
10362 #[test]
10363 fn groovy_jenkinsfile_fixture_extracts_pipeline_block() {
10364 let provider = TreeSitterProvider::new();
10365 let symbols = provider.list_symbols(&fixture_path("Jenkinsfile")).unwrap();
10366
10367 let pipeline = symbols
10368 .iter()
10369 .find(|symbol| symbol.name == "pipeline")
10370 .unwrap_or_else(|| panic!("missing pipeline; got {symbols:?}"));
10371 assert_eq!(pipeline.kind, SymbolKind::Function);
10372 assert_eq!(pipeline.range.start_line, 0);
10373 assert_eq!(pipeline.range.end_line, 9);
10374 }
10375}