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