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