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 current_mtime = std::fs::metadata(file)
6508 .and_then(|m| m.modified())
6509 .map_err(|e| AftError::FileNotFound {
6510 path: format!("{}: {}", file.display(), e),
6511 })?;
6512 let source = std::fs::read_to_string(file).map_err(|e| AftError::FileNotFound {
6513 path: format!("{}: {}", file.display(), e),
6514 })?;
6515 let size = source.len() as u64;
6516 let content_hash = content_hash_for_source(&source);
6517 let (tree, lang) = {
6518 let mut parser = self.parser.borrow_mut();
6519 let (tree, lang) =
6520 parser.parse_with_source(file, &source, current_mtime, size, content_hash)?;
6521 (tree.clone(), lang)
6522 };
6523 Ok((source, tree, lang))
6524 }
6525}
6526
6527fn symbol_matches_in_file(file: &Path, symbols: &[Symbol], name: &str) -> Vec<SymbolMatch> {
6528 symbols
6529 .iter()
6530 .filter(|symbol| symbol.name == name)
6531 .cloned()
6532 .map(|symbol| SymbolMatch {
6533 file: file.display().to_string(),
6534 symbol,
6535 })
6536 .collect()
6537}
6538
6539fn string_content(source: &str, node: &Node) -> Option<String> {
6540 let text = node_text(source, node);
6541 if text.len() < 2 {
6542 return None;
6543 }
6544
6545 Some(
6546 text.trim_start_matches(|c| c == '\'' || c == '"')
6547 .trim_end_matches(|c| c == '\'' || c == '"')
6548 .to_string(),
6549 )
6550}
6551
6552fn find_child_by_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
6553 let mut cursor = node.walk();
6554 if !cursor.goto_first_child() {
6555 return None;
6556 }
6557
6558 loop {
6559 let child = cursor.node();
6560 if child.kind() == kind {
6561 return Some(child);
6562 }
6563 if !cursor.goto_next_sibling() {
6564 break;
6565 }
6566 }
6567
6568 None
6569}
6570
6571fn resolve_export_clause_name(
6572 source: &str,
6573 export_clause: &Node,
6574 requested_name: &str,
6575) -> Option<String> {
6576 let mut cursor = export_clause.walk();
6577 if !cursor.goto_first_child() {
6578 return None;
6579 }
6580
6581 loop {
6582 let child = cursor.node();
6583 if child.kind() == "export_specifier" {
6584 let (source_name, exported_name) = export_specifier_names(source, &child)?;
6585 if exported_name == requested_name {
6586 return Some(source_name);
6587 }
6588 }
6589
6590 if !cursor.goto_next_sibling() {
6591 break;
6592 }
6593 }
6594
6595 None
6596}
6597
6598fn export_specifier_names(source: &str, specifier: &Node) -> Option<(String, String)> {
6599 let source_name = specifier
6600 .child_by_field_name("name")
6601 .map(|node| node_text(source, &node).to_string());
6602 let alias_name = specifier
6603 .child_by_field_name("alias")
6604 .map(|node| node_text(source, &node).to_string());
6605
6606 if let Some(source_name) = source_name {
6607 let exported_name = alias_name.unwrap_or_else(|| source_name.clone());
6608 return Some((source_name, exported_name));
6609 }
6610
6611 let mut names = Vec::new();
6612 let mut cursor = specifier.walk();
6613 if cursor.goto_first_child() {
6614 loop {
6615 let child = cursor.node();
6616 let child_text = node_text(source, &child).trim();
6617 if matches!(
6618 child.kind(),
6619 "identifier" | "type_identifier" | "property_identifier"
6620 ) || child_text == "default"
6621 {
6622 names.push(child_text.to_string());
6623 }
6624 if !cursor.goto_next_sibling() {
6625 break;
6626 }
6627 }
6628 }
6629
6630 match names.as_slice() {
6631 [name] => Some((name.clone(), name.clone())),
6632 [source_name, exported_name, ..] => Some((source_name.clone(), exported_name.clone())),
6633 _ => None,
6634 }
6635}
6636
6637fn export_statement_has_wildcard(source: &str, node: &Node) -> bool {
6638 let mut cursor = node.walk();
6639 if !cursor.goto_first_child() {
6640 return false;
6641 }
6642
6643 loop {
6644 if node_text(source, &cursor.node()).trim() == "*" {
6645 return true;
6646 }
6647 if !cursor.goto_next_sibling() {
6648 break;
6649 }
6650 }
6651
6652 false
6653}
6654
6655fn node_contains_token(source: &str, node: &Node, token: &str) -> bool {
6656 let mut cursor = node.walk();
6657 if !cursor.goto_first_child() {
6658 return false;
6659 }
6660
6661 loop {
6662 if node_text(source, &cursor.node()).trim() == token {
6663 return true;
6664 }
6665 if !cursor.goto_next_sibling() {
6666 break;
6667 }
6668 }
6669
6670 false
6671}
6672
6673fn default_export_target_name(source: &str, export_stmt: &Node) -> Option<String> {
6674 if let Some(value_node) = export_stmt.child_by_field_name("value") {
6675 if let Some(name) = default_export_node_name(source, &value_node) {
6676 return Some(name);
6677 }
6678 }
6679
6680 if let Some(declaration_node) = export_stmt.child_by_field_name("declaration") {
6681 if let Some(name) = default_export_node_name(source, &declaration_node) {
6682 return Some(name);
6683 }
6684 }
6685
6686 let mut cursor = export_stmt.walk();
6687 if !cursor.goto_first_child() {
6688 return None;
6689 }
6690
6691 loop {
6692 let child = cursor.node();
6693 if let Some(name) = default_export_node_name(source, &child) {
6694 return Some(name);
6695 }
6696
6697 if !cursor.goto_next_sibling() {
6698 break;
6699 }
6700 }
6701
6702 None
6703}
6704
6705fn default_export_node_name(source: &str, node: &Node) -> Option<String> {
6706 match node.kind() {
6707 "function_declaration"
6708 | "generator_function_declaration"
6709 | "function_expression"
6710 | "generator_function"
6711 | "class_declaration"
6712 | "class" => node
6713 .child_by_field_name("name")
6714 .map(|name_node| node_text(source, &name_node).to_string())
6715 .or_else(|| Some("default".to_string())),
6716 "interface_declaration" | "enum_declaration" | "type_alias_declaration" => node
6717 .child_by_field_name("name")
6718 .map(|name_node| node_text(source, &name_node).to_string()),
6719 "lexical_declaration" => lexical_declaration_name(source, node),
6720 "identifier" | "type_identifier" => {
6721 let text = node_text(source, node);
6722 (text != "export" && text != "default").then(|| text.to_string())
6723 }
6724 _ => None,
6725 }
6726}
6727
6728fn lexical_declaration_name(source: &str, node: &Node) -> Option<String> {
6729 let mut cursor = node.walk();
6730 if !cursor.goto_first_child() {
6731 return None;
6732 }
6733
6734 loop {
6735 let child = cursor.node();
6736 if child.kind() == "variable_declarator" {
6737 if let Some(name_node) = child.child_by_field_name("name") {
6738 return Some(node_text(source, &name_node).to_string());
6739 }
6740 }
6741 if !cursor.goto_next_sibling() {
6742 break;
6743 }
6744 }
6745
6746 None
6747}
6748
6749impl crate::language::LanguageProvider for TreeSitterProvider {
6750 fn resolve_symbol(&self, file: &Path, name: &str) -> Result<Vec<SymbolMatch>, AftError> {
6751 let matches = self.resolve_symbol_inner(file, name, 0, &mut HashSet::new())?;
6752
6753 if matches.is_empty() {
6754 Err(AftError::SymbolNotFound {
6755 name: name.to_string(),
6756 file: file.display().to_string(),
6757 })
6758 } else {
6759 Ok(matches)
6760 }
6761 }
6762
6763 fn list_symbols(&self, file: &Path) -> Result<Vec<Symbol>, AftError> {
6764 self.parser.borrow_mut().extract_symbols(file)
6765 }
6766
6767 fn as_any(&self) -> &dyn std::any::Any {
6768 self
6769 }
6770}
6771
6772#[cfg(test)]
6773mod tests {
6774 use super::*;
6775 use crate::language::LanguageProvider;
6776 use crate::symbol_cache_disk;
6777 use std::path::{Path, PathBuf};
6778
6779 fn fixture_path(name: &str) -> PathBuf {
6780 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
6781 .join("tests")
6782 .join("fixtures")
6783 .join(name)
6784 }
6785
6786 fn test_symbol(name: &str) -> Symbol {
6787 Symbol {
6788 name: name.to_string(),
6789 kind: SymbolKind::Function,
6790 range: Range {
6791 start_line: 0,
6792 start_col: 0,
6793 end_line: 0,
6794 end_col: 10,
6795 },
6796 signature: Some(format!("fn {name}()")),
6797 scope_chain: Vec::new(),
6798 exported: true,
6799 parent: None,
6800 }
6801 }
6802
6803 #[test]
6804 fn inc_and_scss_extensions_are_detected() {
6805 assert_eq!(
6806 detect_language(Path::new("template.inc")),
6807 Some(LangId::Php)
6808 );
6809 assert_eq!(
6810 detect_language(Path::new("styles.scss")),
6811 Some(LangId::Scss)
6812 );
6813 assert_eq!(detect_language(Path::new("main.pas")), Some(LangId::Pascal));
6814 assert_eq!(detect_language(Path::new("main.pp")), Some(LangId::Pascal));
6815 assert_eq!(detect_language(Path::new("main.dpr")), Some(LangId::Pascal));
6816 assert_eq!(detect_language(Path::new("main.dpk")), Some(LangId::Pascal));
6817 assert_eq!(detect_language(Path::new("main.lpr")), Some(LangId::Pascal));
6818 assert_eq!(detect_language(Path::new("template.tpl")), None);
6819 }
6820
6821 #[test]
6822 fn inc_files_parse_with_php_grammar() {
6823 let tmp = tempfile::tempdir().expect("create temp dir");
6824 let file = tmp.path().join("partial.inc");
6825 std::fs::write(&file, "<?php\nfunction render_partial() { return 1; }\n")
6826 .expect("write inc file");
6827
6828 let mut parser = FileParser::new();
6829 let symbols = parser
6830 .extract_symbols(&file)
6831 .expect("extract php inc symbols");
6832 let function = symbols
6833 .iter()
6834 .find(|symbol| symbol.name == "render_partial")
6835 .expect("find PHP function in .inc");
6836 assert_eq!(function.kind, SymbolKind::Function);
6837 }
6838
6839 #[test]
6840 fn scss_symbols_include_mixin_variable_function_and_rule() {
6841 let tmp = tempfile::tempdir().expect("create temp dir");
6842 let file = tmp.path().join("styles.scss");
6843 std::fs::write(
6844 &file,
6845 r#"$brand-color: #336699;
6846
6847@mixin button-base($padding) {
6848 padding: $padding;
6849}
6850
6851@function double($value) {
6852 @return $value * 2;
6853}
6854
6855.card, .panel {
6856 color: $brand-color;
6857}
6858"#,
6859 )
6860 .expect("write scss file");
6861
6862 let mut parser = FileParser::new();
6863 let symbols = parser.extract_symbols(&file).expect("extract scss symbols");
6864 let get = |name: &str| {
6865 symbols
6866 .iter()
6867 .find(|symbol| symbol.name == name)
6868 .unwrap_or_else(|| panic!("missing {name}; got {symbols:?}"))
6869 };
6870
6871 assert_eq!(get("button-base").kind, SymbolKind::Function);
6872 assert_eq!(get("double").kind, SymbolKind::Function);
6873 assert_eq!(get("$brand-color").kind, SymbolKind::Variable);
6874 assert_eq!(get(".card, .panel").kind, SymbolKind::Class);
6875 }
6876
6877 #[test]
6878 fn symbol_cache_load_from_disk_round_trips_synthetic_entry() {
6879 let project = tempfile::tempdir().expect("create project dir");
6880 let storage = tempfile::tempdir().expect("create storage dir");
6881 let source = project.path().join("src/lib.rs");
6882 std::fs::create_dir_all(source.parent().expect("source parent"))
6883 .expect("create source dir");
6884 std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
6885 let mtime = std::fs::metadata(&source)
6886 .expect("stat source")
6887 .modified()
6888 .expect("source mtime");
6889 let content = std::fs::read(&source).expect("read source");
6890 let size = content.len() as u64;
6891 let hash = crate::cache_freshness::hash_bytes(&content);
6892
6893 let mut cache = SymbolCache::new();
6894 cache.set_project_root(project.path().to_path_buf());
6895 cache.insert(
6896 source.clone(),
6897 mtime,
6898 size,
6899 hash,
6900 vec![test_symbol("cached")],
6901 );
6902 symbol_cache_disk::write_to_disk(&cache, storage.path(), "unit-project")
6903 .expect("write symbol cache");
6904
6905 let mut restored = SymbolCache::new();
6906 let loaded = restored.load_from_disk(storage.path(), "unit-project", project.path());
6907 let symbols = restored.get(&source, mtime).expect("restored symbols");
6908
6909 assert_eq!(loaded, 1);
6910 assert_eq!(symbols.len(), 1);
6911 assert_eq!(symbols[0].name, "cached");
6912 }
6913
6914 #[test]
6915 fn symbol_cache_load_from_disk_drops_stale_synthetic_entry() {
6916 let project = tempfile::tempdir().expect("create project dir");
6917 let storage = tempfile::tempdir().expect("create storage dir");
6918 let source = project.path().join("src/lib.rs");
6919 std::fs::create_dir_all(source.parent().expect("source parent"))
6920 .expect("create source dir");
6921 std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
6922 let mtime = std::fs::metadata(&source)
6923 .expect("stat source")
6924 .modified()
6925 .expect("source mtime");
6926 let content = std::fs::read(&source).expect("read source");
6927 let size = content.len() as u64;
6928 let hash = crate::cache_freshness::hash_bytes(&content);
6929
6930 let mut cache = SymbolCache::new();
6931 cache.set_project_root(project.path().to_path_buf());
6932 cache.insert(
6933 source.clone(),
6934 mtime,
6935 size,
6936 hash,
6937 vec![test_symbol("cached")],
6938 );
6939 symbol_cache_disk::write_to_disk(&cache, storage.path(), "stale-unit-project")
6940 .expect("write symbol cache");
6941
6942 std::fs::write(&source, "pub fn cached() {}\npub fn fresh() {}\n").expect("change source");
6943
6944 let mut restored = SymbolCache::new();
6945 let loaded = restored.load_from_disk(storage.path(), "stale-unit-project", project.path());
6946
6947 assert_eq!(loaded, 0);
6948 assert_eq!(restored.len(), 0);
6949 }
6950
6951 #[test]
6952 fn stale_prewarm_generation_cannot_repopulate_symbol_cache_after_reset() {
6953 let project = tempfile::tempdir().expect("create project dir");
6954 let storage = tempfile::tempdir().expect("create storage dir");
6955 let source = project.path().join("src/lib.rs");
6956 std::fs::create_dir_all(source.parent().expect("source parent"))
6957 .expect("create source dir");
6958 std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
6959 let mtime = std::fs::metadata(&source)
6960 .expect("stat source")
6961 .modified()
6962 .expect("source mtime");
6963 let content = std::fs::read(&source).expect("read source");
6964 let size = content.len() as u64;
6965 let hash = crate::cache_freshness::hash_bytes(&content);
6966
6967 let mut disk_cache = SymbolCache::new();
6968 disk_cache.set_project_root(project.path().to_path_buf());
6969 disk_cache.insert(
6970 source.clone(),
6971 mtime,
6972 size,
6973 hash,
6974 vec![test_symbol("cached")],
6975 );
6976 symbol_cache_disk::write_to_disk(&disk_cache, storage.path(), "prewarm-reset")
6977 .expect("write symbol cache");
6978
6979 let shared = Arc::new(RwLock::new(SymbolCache::new()));
6980 let stale_generation = shared.write().unwrap().reset();
6981 let active_generation = shared.write().unwrap().reset();
6982 assert_ne!(stale_generation, active_generation);
6983
6984 {
6985 let mut cache = shared.write().unwrap();
6986 assert!(!cache
6987 .set_project_root_for_generation(stale_generation, project.path().to_path_buf()));
6988 assert_eq!(
6989 cache.load_from_disk_for_generation(
6990 stale_generation,
6991 storage.path(),
6992 "prewarm-reset",
6993 project.path()
6994 ),
6995 0
6996 );
6997 }
6998
6999 let mut stale_parser =
7000 FileParser::with_symbol_cache_generation(Arc::clone(&shared), Some(stale_generation));
7001 stale_parser
7002 .extract_symbols(&source)
7003 .expect("stale prewarm parses source but must not write cache");
7004
7005 let cache = shared.read().unwrap();
7006 assert_eq!(cache.generation(), active_generation);
7007 assert_eq!(cache.len(), 0);
7008 assert!(cache.project_root().is_none());
7009 assert!(!cache.contains_key(&source));
7010 }
7011
7012 #[test]
7015 fn detect_ts() {
7016 assert_eq!(
7017 detect_language(Path::new("foo.ts")),
7018 Some(LangId::TypeScript)
7019 );
7020 }
7021
7022 #[test]
7023 fn detect_tsx() {
7024 assert_eq!(detect_language(Path::new("foo.tsx")), Some(LangId::Tsx));
7025 }
7026
7027 #[test]
7028 fn detect_js() {
7029 assert_eq!(
7030 detect_language(Path::new("foo.js")),
7031 Some(LangId::JavaScript)
7032 );
7033 }
7034
7035 #[test]
7036 fn detect_jsx() {
7037 assert_eq!(
7038 detect_language(Path::new("foo.jsx")),
7039 Some(LangId::JavaScript)
7040 );
7041 }
7042
7043 #[test]
7044 fn detect_py() {
7045 assert_eq!(detect_language(Path::new("foo.py")), Some(LangId::Python));
7046 }
7047
7048 #[test]
7049 fn detect_rs() {
7050 assert_eq!(detect_language(Path::new("foo.rs")), Some(LangId::Rust));
7051 }
7052
7053 #[test]
7054 fn detect_go() {
7055 assert_eq!(detect_language(Path::new("foo.go")), Some(LangId::Go));
7056 }
7057
7058 #[test]
7059 fn detect_c() {
7060 assert_eq!(detect_language(Path::new("foo.c")), Some(LangId::C));
7061 }
7062
7063 #[test]
7064 fn detect_h() {
7065 assert_eq!(detect_language(Path::new("foo.h")), Some(LangId::C));
7066 }
7067
7068 #[test]
7069 fn detect_cc() {
7070 assert_eq!(detect_language(Path::new("foo.cc")), Some(LangId::Cpp));
7071 }
7072
7073 #[test]
7074 fn detect_cpp() {
7075 assert_eq!(detect_language(Path::new("foo.cpp")), Some(LangId::Cpp));
7076 }
7077
7078 #[test]
7079 fn detect_cxx() {
7080 assert_eq!(detect_language(Path::new("foo.cxx")), Some(LangId::Cpp));
7081 }
7082
7083 #[test]
7084 fn detect_hpp() {
7085 assert_eq!(detect_language(Path::new("foo.hpp")), Some(LangId::Cpp));
7086 }
7087
7088 #[test]
7089 fn detect_hh() {
7090 assert_eq!(detect_language(Path::new("foo.hh")), Some(LangId::Cpp));
7091 }
7092
7093 #[test]
7094 fn detect_zig() {
7095 assert_eq!(detect_language(Path::new("foo.zig")), Some(LangId::Zig));
7096 }
7097
7098 #[test]
7099 fn detect_cs() {
7100 assert_eq!(detect_language(Path::new("foo.cs")), Some(LangId::CSharp));
7101 }
7102
7103 #[test]
7104 fn detect_unknown_returns_none() {
7105 assert_eq!(detect_language(Path::new("foo.txt")), None);
7106 }
7107
7108 #[test]
7111 fn unsupported_extension_returns_invalid_request() {
7112 let path = fixture_path("sample.ts");
7114 let bad_path = path.with_extension("txt");
7115 std::fs::write(&bad_path, "hello").unwrap();
7117 let provider = TreeSitterProvider::new();
7118 let result = provider.list_symbols(&bad_path);
7119 std::fs::remove_file(&bad_path).ok();
7120 match result {
7121 Err(AftError::InvalidRequest { message }) => {
7122 assert!(
7123 message.contains("unsupported file extension"),
7124 "msg: {}",
7125 message
7126 );
7127 assert!(message.contains("txt"), "msg: {}", message);
7128 }
7129 other => panic!("expected InvalidRequest, got {:?}", other),
7130 }
7131 }
7132
7133 #[test]
7136 fn ts_extracts_all_symbol_kinds() {
7137 let provider = TreeSitterProvider::new();
7138 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
7139
7140 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
7141 assert!(
7142 names.contains(&"greet"),
7143 "missing function greet: {:?}",
7144 names
7145 );
7146 assert!(names.contains(&"add"), "missing arrow fn add: {:?}", names);
7147 assert!(
7148 names.contains(&"UserService"),
7149 "missing class UserService: {:?}",
7150 names
7151 );
7152 assert!(
7153 names.contains(&"Config"),
7154 "missing interface Config: {:?}",
7155 names
7156 );
7157 assert!(
7158 names.contains(&"Status"),
7159 "missing enum Status: {:?}",
7160 names
7161 );
7162 assert!(
7163 names.contains(&"UserId"),
7164 "missing type alias UserId: {:?}",
7165 names
7166 );
7167 assert!(
7168 names.contains(&"internalHelper"),
7169 "missing non-exported fn: {:?}",
7170 names
7171 );
7172
7173 assert!(
7175 symbols.len() >= 6,
7176 "expected ≥6 symbols, got {}: {:?}",
7177 symbols.len(),
7178 names
7179 );
7180 }
7181
7182 #[test]
7183 fn ts_symbol_kinds_correct() {
7184 let provider = TreeSitterProvider::new();
7185 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
7186
7187 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7188
7189 assert_eq!(find("greet").kind, SymbolKind::Function);
7190 assert_eq!(find("add").kind, SymbolKind::Function); assert_eq!(find("UserService").kind, SymbolKind::Class);
7192 assert_eq!(find("Config").kind, SymbolKind::Interface);
7193 assert_eq!(find("Status").kind, SymbolKind::Enum);
7194 assert_eq!(find("UserId").kind, SymbolKind::TypeAlias);
7195 }
7196
7197 #[test]
7198 fn ts_export_detection() {
7199 let provider = TreeSitterProvider::new();
7200 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
7201
7202 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7203
7204 assert!(find("greet").exported, "greet should be exported");
7205 assert!(find("add").exported, "add should be exported");
7206 assert!(
7207 find("UserService").exported,
7208 "UserService should be exported"
7209 );
7210 assert!(find("Config").exported, "Config should be exported");
7211 assert!(find("Status").exported, "Status should be exported");
7212 assert!(
7213 !find("internalHelper").exported,
7214 "internalHelper should not be exported"
7215 );
7216 }
7217
7218 #[test]
7219 fn ts_method_scope_chain() {
7220 let provider = TreeSitterProvider::new();
7221 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
7222
7223 let methods: Vec<&Symbol> = symbols
7224 .iter()
7225 .filter(|s| s.kind == SymbolKind::Method)
7226 .collect();
7227 assert!(!methods.is_empty(), "should have at least one method");
7228
7229 for method in &methods {
7230 assert_eq!(
7231 method.scope_chain,
7232 vec!["UserService"],
7233 "method {} should have UserService in scope chain",
7234 method.name
7235 );
7236 assert_eq!(method.parent.as_deref(), Some("UserService"));
7237 }
7238 }
7239
7240 #[test]
7241 fn ts_signatures_present() {
7242 let provider = TreeSitterProvider::new();
7243 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
7244
7245 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7246
7247 let greet_sig = find("greet").signature.as_ref().unwrap();
7248 assert!(
7249 greet_sig.contains("greet"),
7250 "signature should contain function name: {}",
7251 greet_sig
7252 );
7253 }
7254
7255 #[test]
7256 fn ts_ranges_valid() {
7257 let provider = TreeSitterProvider::new();
7258 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
7259
7260 for s in &symbols {
7261 assert!(
7262 s.range.end_line >= s.range.start_line,
7263 "symbol {} has invalid range: {:?}",
7264 s.name,
7265 s.range
7266 );
7267 }
7268 }
7269
7270 #[test]
7273 fn js_extracts_core_symbols() {
7274 let provider = TreeSitterProvider::new();
7275 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
7276
7277 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
7278 assert!(
7279 names.contains(&"multiply"),
7280 "missing function multiply: {:?}",
7281 names
7282 );
7283 assert!(
7284 names.contains(&"divide"),
7285 "missing arrow fn divide: {:?}",
7286 names
7287 );
7288 assert!(
7289 names.contains(&"EventEmitter"),
7290 "missing class EventEmitter: {:?}",
7291 names
7292 );
7293 assert!(
7294 names.contains(&"main"),
7295 "missing default export fn main: {:?}",
7296 names
7297 );
7298
7299 assert!(
7300 symbols.len() >= 4,
7301 "expected ≥4 symbols, got {}: {:?}",
7302 symbols.len(),
7303 names
7304 );
7305 }
7306
7307 #[test]
7308 fn js_arrow_fn_correctly_named() {
7309 let provider = TreeSitterProvider::new();
7310 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
7311
7312 let divide = symbols.iter().find(|s| s.name == "divide").unwrap();
7313 assert_eq!(divide.kind, SymbolKind::Function);
7314 assert!(divide.exported, "divide should be exported");
7315
7316 let internal = symbols.iter().find(|s| s.name == "internalUtil").unwrap();
7317 assert_eq!(internal.kind, SymbolKind::Function);
7318 assert!(!internal.exported, "internalUtil should not be exported");
7319 }
7320
7321 #[test]
7322 fn js_method_scope_chain() {
7323 let provider = TreeSitterProvider::new();
7324 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
7325
7326 let methods: Vec<&Symbol> = symbols
7327 .iter()
7328 .filter(|s| s.kind == SymbolKind::Method)
7329 .collect();
7330
7331 for method in &methods {
7332 assert_eq!(
7333 method.scope_chain,
7334 vec!["EventEmitter"],
7335 "method {} should have EventEmitter in scope chain",
7336 method.name
7337 );
7338 }
7339 }
7340
7341 #[test]
7344 fn tsx_extracts_react_component() {
7345 let provider = TreeSitterProvider::new();
7346 let symbols = provider.list_symbols(&fixture_path("sample.tsx")).unwrap();
7347
7348 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
7349 assert!(
7350 names.contains(&"Button"),
7351 "missing React component Button: {:?}",
7352 names
7353 );
7354 assert!(
7355 names.contains(&"Counter"),
7356 "missing class Counter: {:?}",
7357 names
7358 );
7359 assert!(
7360 names.contains(&"formatLabel"),
7361 "missing function formatLabel: {:?}",
7362 names
7363 );
7364
7365 assert!(
7366 symbols.len() >= 2,
7367 "expected ≥2 symbols, got {}: {:?}",
7368 symbols.len(),
7369 names
7370 );
7371 }
7372
7373 #[test]
7374 fn tsx_jsx_doesnt_break_parser() {
7375 let provider = TreeSitterProvider::new();
7377 let result = provider.list_symbols(&fixture_path("sample.tsx"));
7378 assert!(
7379 result.is_ok(),
7380 "TSX parsing should succeed: {:?}",
7381 result.err()
7382 );
7383 }
7384
7385 #[test]
7388 fn resolve_symbol_finds_match() {
7389 let provider = TreeSitterProvider::new();
7390 let matches = provider
7391 .resolve_symbol(&fixture_path("sample.ts"), "greet")
7392 .unwrap();
7393 assert_eq!(matches.len(), 1);
7394 assert_eq!(matches[0].symbol.name, "greet");
7395 assert_eq!(matches[0].symbol.kind, SymbolKind::Function);
7396 }
7397
7398 #[test]
7399 fn resolve_symbol_not_found() {
7400 let provider = TreeSitterProvider::new();
7401 let result = provider.resolve_symbol(&fixture_path("sample.ts"), "nonexistent");
7402 assert!(matches!(result, Err(AftError::SymbolNotFound { .. })));
7403 }
7404
7405 #[test]
7406 fn resolve_symbol_follows_reexport_chains() {
7407 let dir = tempfile::tempdir().unwrap();
7408 let config = dir.path().join("config.ts");
7409 let barrel1 = dir.path().join("barrel1.ts");
7410 let barrel2 = dir.path().join("barrel2.ts");
7411 let barrel3 = dir.path().join("barrel3.ts");
7412 let index = dir.path().join("index.ts");
7413
7414 std::fs::write(
7415 &config,
7416 "export class Config {}\nexport default class DefaultConfig {}\n",
7417 )
7418 .unwrap();
7419 std::fs::write(
7420 &barrel1,
7421 "export { Config } from './config';\nexport { default as NamedDefault } from './config';\n",
7422 )
7423 .unwrap();
7424 std::fs::write(
7425 &barrel2,
7426 "export { Config as RenamedConfig } from './barrel1';\n",
7427 )
7428 .unwrap();
7429 std::fs::write(
7430 &barrel3,
7431 "export * from './barrel2';\nexport * from './barrel1';\n",
7432 )
7433 .unwrap();
7434 std::fs::write(
7435 &index,
7436 "export class Config {}\nexport { RenamedConfig as FinalConfig } from './barrel3';\nexport * from './barrel3';\n",
7437 )
7438 .unwrap();
7439
7440 let provider = TreeSitterProvider::new();
7441 let config_canon = std::fs::canonicalize(&config).unwrap();
7442
7443 let direct = provider.resolve_symbol(&barrel1, "Config").unwrap();
7444 assert_eq!(direct.len(), 1);
7445 assert_eq!(direct[0].symbol.name, "Config");
7446 assert_eq!(direct[0].file, config_canon.display().to_string());
7447
7448 let renamed = provider.resolve_symbol(&barrel2, "RenamedConfig").unwrap();
7449 assert_eq!(renamed.len(), 1);
7450 assert_eq!(renamed[0].symbol.name, "Config");
7451 assert_eq!(renamed[0].file, config_canon.display().to_string());
7452
7453 let wildcard_chain = provider.resolve_symbol(&index, "FinalConfig").unwrap();
7454 assert_eq!(wildcard_chain.len(), 1);
7455 assert_eq!(wildcard_chain[0].symbol.name, "Config");
7456 assert_eq!(wildcard_chain[0].file, config_canon.display().to_string());
7457
7458 let wildcard_default = provider.resolve_symbol(&index, "NamedDefault").unwrap();
7459 assert_eq!(wildcard_default.len(), 1);
7460 assert_eq!(wildcard_default[0].symbol.name, "DefaultConfig");
7461 assert_eq!(wildcard_default[0].file, config_canon.display().to_string());
7462
7463 let local = provider.resolve_symbol(&index, "Config").unwrap();
7464 assert_eq!(local.len(), 1);
7465 assert_eq!(local[0].symbol.name, "Config");
7466 assert_eq!(local[0].file, index.display().to_string());
7467 }
7468
7469 #[test]
7472 fn symbol_range_includes_rust_attributes() {
7473 let dir = tempfile::tempdir().unwrap();
7474 let path = dir.path().join("test_attrs.rs");
7475 std::fs::write(
7476 &path,
7477 "/// This is a doc comment\n#[test]\n#[cfg(test)]\nfn my_test_fn() {\n assert!(true);\n}\n",
7478 )
7479 .unwrap();
7480
7481 let provider = TreeSitterProvider::new();
7482 let matches = provider.resolve_symbol(&path, "my_test_fn").unwrap();
7483 assert_eq!(matches.len(), 1);
7484 assert_eq!(
7485 matches[0].symbol.range.start_line, 0,
7486 "symbol range should include preceding /// doc comment, got start_line={}",
7487 matches[0].symbol.range.start_line
7488 );
7489 }
7490
7491 #[test]
7492 fn symbol_range_includes_go_doc_comment() {
7493 let dir = tempfile::tempdir().unwrap();
7494 let path = dir.path().join("test_doc.go");
7495 std::fs::write(
7496 &path,
7497 "package main\n\n// MyFunc does something useful.\n// It has a multi-line doc.\nfunc MyFunc() {\n}\n",
7498 )
7499 .unwrap();
7500
7501 let provider = TreeSitterProvider::new();
7502 let matches = provider.resolve_symbol(&path, "MyFunc").unwrap();
7503 assert_eq!(matches.len(), 1);
7504 assert_eq!(
7505 matches[0].symbol.range.start_line, 2,
7506 "symbol range should include preceding doc comments, got start_line={}",
7507 matches[0].symbol.range.start_line
7508 );
7509 }
7510
7511 #[test]
7512 fn symbol_range_skips_unrelated_comments() {
7513 let dir = tempfile::tempdir().unwrap();
7514 let path = dir.path().join("test_gap.go");
7515 std::fs::write(
7516 &path,
7517 "package main\n\n// This is a standalone comment\n\nfunc Standalone() {\n}\n",
7518 )
7519 .unwrap();
7520
7521 let provider = TreeSitterProvider::new();
7522 let matches = provider.resolve_symbol(&path, "Standalone").unwrap();
7523 assert_eq!(matches.len(), 1);
7524 assert_eq!(
7525 matches[0].symbol.range.start_line, 4,
7526 "symbol range should NOT include comment separated by blank line, got start_line={}",
7527 matches[0].symbol.range.start_line
7528 );
7529 }
7530
7531 #[test]
7532 fn parse_cache_returns_same_tree() {
7533 let mut parser = FileParser::new();
7534 let path = fixture_path("sample.ts");
7535
7536 let (tree1, _) = parser.parse(&path).unwrap();
7537 let tree1_root = tree1.root_node().byte_range();
7538
7539 let (tree2, _) = parser.parse(&path).unwrap();
7540 let tree2_root = tree2.root_node().byte_range();
7541
7542 assert_eq!(tree1_root, tree2_root);
7544 }
7545
7546 #[test]
7547 fn extract_symbols_from_tree_matches_list_symbols() {
7548 let path = fixture_path("sample.rs");
7549 let source = std::fs::read_to_string(&path).unwrap();
7550
7551 let provider = TreeSitterProvider::new();
7552 let listed = provider.list_symbols(&path).unwrap();
7553
7554 let mut parser = FileParser::new();
7555 let (tree, lang) = parser.parse(&path).unwrap();
7556 let extracted = extract_symbols_from_tree(&source, tree, lang).unwrap();
7557
7558 assert_eq!(symbols_as_debug(&extracted), symbols_as_debug(&listed));
7559 }
7560
7561 fn symbols_as_debug(symbols: &[Symbol]) -> Vec<String> {
7562 symbols
7563 .iter()
7564 .map(|symbol| {
7565 format!(
7566 "{}|{:?}|{}:{}-{}:{}|{:?}|{:?}|{}|{:?}",
7567 symbol.name,
7568 symbol.kind,
7569 symbol.range.start_line,
7570 symbol.range.start_col,
7571 symbol.range.end_line,
7572 symbol.range.end_col,
7573 symbol.signature,
7574 symbol.scope_chain,
7575 symbol.exported,
7576 symbol.parent,
7577 )
7578 })
7579 .collect()
7580 }
7581
7582 #[test]
7585 fn py_extracts_all_symbols() {
7586 let provider = TreeSitterProvider::new();
7587 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
7588
7589 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
7590 assert!(
7591 names.contains(&"top_level_function"),
7592 "missing top_level_function: {:?}",
7593 names
7594 );
7595 assert!(names.contains(&"MyClass"), "missing MyClass: {:?}", names);
7596 assert!(
7597 names.contains(&"instance_method"),
7598 "missing method instance_method: {:?}",
7599 names
7600 );
7601 assert!(
7602 names.contains(&"decorated_function"),
7603 "missing decorated_function: {:?}",
7604 names
7605 );
7606
7607 assert!(
7609 symbols.len() >= 4,
7610 "expected ≥4 symbols, got {}: {:?}",
7611 symbols.len(),
7612 names
7613 );
7614 }
7615
7616 #[test]
7617 fn py_symbol_kinds_correct() {
7618 let provider = TreeSitterProvider::new();
7619 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
7620
7621 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7622
7623 assert_eq!(find("top_level_function").kind, SymbolKind::Function);
7624 assert_eq!(find("MyClass").kind, SymbolKind::Class);
7625 assert_eq!(find("instance_method").kind, SymbolKind::Method);
7626 assert_eq!(find("decorated_function").kind, SymbolKind::Function);
7627 assert_eq!(find("OuterClass").kind, SymbolKind::Class);
7628 assert_eq!(find("InnerClass").kind, SymbolKind::Class);
7629 assert_eq!(find("inner_method").kind, SymbolKind::Method);
7630 assert_eq!(find("outer_method").kind, SymbolKind::Method);
7631 }
7632
7633 #[test]
7634 fn py_method_scope_chain() {
7635 let provider = TreeSitterProvider::new();
7636 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
7637
7638 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7639
7640 assert_eq!(
7642 find("instance_method").scope_chain,
7643 vec!["MyClass"],
7644 "instance_method should have MyClass in scope chain"
7645 );
7646 assert_eq!(find("instance_method").parent.as_deref(), Some("MyClass"));
7647
7648 assert_eq!(
7650 find("inner_method").scope_chain,
7651 vec!["OuterClass", "InnerClass"],
7652 "inner_method should have nested scope chain"
7653 );
7654
7655 assert_eq!(
7657 find("InnerClass").scope_chain,
7658 vec!["OuterClass"],
7659 "InnerClass should have OuterClass in scope"
7660 );
7661
7662 assert!(
7664 find("top_level_function").scope_chain.is_empty(),
7665 "top-level function should have empty scope chain"
7666 );
7667 }
7668
7669 #[test]
7670 fn py_decorated_function_signature() {
7671 let provider = TreeSitterProvider::new();
7672 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
7673
7674 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7675
7676 let sig = find("decorated_function").signature.as_ref().unwrap();
7677 assert!(
7678 sig.contains("@staticmethod"),
7679 "decorated function signature should include decorator: {}",
7680 sig
7681 );
7682 assert!(
7683 sig.contains("def decorated_function"),
7684 "signature should include function def: {}",
7685 sig
7686 );
7687 }
7688
7689 #[test]
7690 fn py_ranges_valid() {
7691 let provider = TreeSitterProvider::new();
7692 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
7693
7694 for s in &symbols {
7695 assert!(
7696 s.range.end_line >= s.range.start_line,
7697 "symbol {} has invalid range: {:?}",
7698 s.name,
7699 s.range
7700 );
7701 }
7702 }
7703
7704 #[test]
7707 fn rs_extracts_all_symbols() {
7708 let provider = TreeSitterProvider::new();
7709 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
7710
7711 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
7712 assert!(
7713 names.contains(&"public_function"),
7714 "missing public_function: {:?}",
7715 names
7716 );
7717 assert!(
7718 names.contains(&"private_function"),
7719 "missing private_function: {:?}",
7720 names
7721 );
7722 assert!(names.contains(&"MyStruct"), "missing MyStruct: {:?}", names);
7723 assert!(names.contains(&"Color"), "missing enum Color: {:?}", names);
7724 assert!(
7725 names.contains(&"Drawable"),
7726 "missing trait Drawable: {:?}",
7727 names
7728 );
7729 assert!(
7731 names.contains(&"new"),
7732 "missing impl method new: {:?}",
7733 names
7734 );
7735
7736 assert!(
7738 symbols.len() >= 6,
7739 "expected ≥6 symbols, got {}: {:?}",
7740 symbols.len(),
7741 names
7742 );
7743 }
7744
7745 #[test]
7746 fn rs_symbol_kinds_correct() {
7747 let provider = TreeSitterProvider::new();
7748 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
7749
7750 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7751
7752 assert_eq!(find("public_function").kind, SymbolKind::Function);
7753 assert_eq!(find("private_function").kind, SymbolKind::Function);
7754 assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
7755 assert_eq!(find("Color").kind, SymbolKind::Enum);
7756 assert_eq!(find("Drawable").kind, SymbolKind::Interface); assert_eq!(find("new").kind, SymbolKind::Method);
7758 }
7759
7760 #[test]
7761 fn rs_pub_export_detection() {
7762 let provider = TreeSitterProvider::new();
7763 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
7764
7765 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7766
7767 assert!(
7768 find("public_function").exported,
7769 "pub fn should be exported"
7770 );
7771 assert!(
7772 !find("private_function").exported,
7773 "non-pub fn should not be exported"
7774 );
7775 assert!(find("MyStruct").exported, "pub struct should be exported");
7776 assert!(find("Color").exported, "pub enum should be exported");
7777 assert!(find("Drawable").exported, "pub trait should be exported");
7778 assert!(
7779 find("new").exported,
7780 "pub fn inside impl should be exported"
7781 );
7782 assert!(
7783 !find("helper").exported,
7784 "non-pub fn inside impl should not be exported"
7785 );
7786 }
7787
7788 #[test]
7789 fn rs_impl_method_scope_chain() {
7790 let provider = TreeSitterProvider::new();
7791 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
7792
7793 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7794
7795 assert_eq!(
7797 find("new").scope_chain,
7798 vec!["MyStruct"],
7799 "impl method should have type in scope chain"
7800 );
7801 assert_eq!(find("new").parent.as_deref(), Some("MyStruct"));
7802
7803 assert!(
7805 find("public_function").scope_chain.is_empty(),
7806 "free function should have empty scope chain"
7807 );
7808 }
7809
7810 #[test]
7811 fn rs_trait_impl_scope_chain() {
7812 let provider = TreeSitterProvider::new();
7813 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
7814
7815 let draw = symbols.iter().find(|s| s.name == "draw").unwrap();
7817 assert_eq!(
7818 draw.scope_chain,
7819 vec!["Drawable for MyStruct"],
7820 "trait impl method should have 'Trait for Type' scope"
7821 );
7822 assert_eq!(draw.parent.as_deref(), Some("MyStruct"));
7823 }
7824
7825 #[test]
7826 fn rs_ranges_valid() {
7827 let provider = TreeSitterProvider::new();
7828 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
7829
7830 for s in &symbols {
7831 assert!(
7832 s.range.end_line >= s.range.start_line,
7833 "symbol {} has invalid range: {:?}",
7834 s.name,
7835 s.range
7836 );
7837 }
7838 }
7839
7840 #[test]
7843 fn go_extracts_all_symbols() {
7844 let provider = TreeSitterProvider::new();
7845 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
7846
7847 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
7848 assert!(
7849 names.contains(&"ExportedFunction"),
7850 "missing ExportedFunction: {:?}",
7851 names
7852 );
7853 assert!(
7854 names.contains(&"unexportedFunction"),
7855 "missing unexportedFunction: {:?}",
7856 names
7857 );
7858 assert!(
7859 names.contains(&"MyStruct"),
7860 "missing struct MyStruct: {:?}",
7861 names
7862 );
7863 assert!(
7864 names.contains(&"Reader"),
7865 "missing interface Reader: {:?}",
7866 names
7867 );
7868 assert!(
7870 names.contains(&"String"),
7871 "missing receiver method String: {:?}",
7872 names
7873 );
7874
7875 assert!(
7877 symbols.len() >= 4,
7878 "expected ≥4 symbols, got {}: {:?}",
7879 symbols.len(),
7880 names
7881 );
7882 }
7883
7884 #[test]
7885 fn go_symbol_kinds_correct() {
7886 let provider = TreeSitterProvider::new();
7887 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
7888
7889 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7890
7891 assert_eq!(find("ExportedFunction").kind, SymbolKind::Function);
7892 assert_eq!(find("unexportedFunction").kind, SymbolKind::Function);
7893 assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
7894 assert_eq!(find("Reader").kind, SymbolKind::Interface);
7895 assert_eq!(find("String").kind, SymbolKind::Method);
7896 assert_eq!(find("helper").kind, SymbolKind::Method);
7897 }
7898
7899 #[test]
7900 fn go_uppercase_export_detection() {
7901 let provider = TreeSitterProvider::new();
7902 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
7903
7904 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7905
7906 assert!(
7907 find("ExportedFunction").exported,
7908 "ExportedFunction (uppercase) should be exported"
7909 );
7910 assert!(
7911 !find("unexportedFunction").exported,
7912 "unexportedFunction (lowercase) should not be exported"
7913 );
7914 assert!(
7915 find("MyStruct").exported,
7916 "MyStruct (uppercase) should be exported"
7917 );
7918 assert!(
7919 find("Reader").exported,
7920 "Reader (uppercase) should be exported"
7921 );
7922 assert!(
7923 find("String").exported,
7924 "String method (uppercase) should be exported"
7925 );
7926 assert!(
7927 !find("helper").exported,
7928 "helper method (lowercase) should not be exported"
7929 );
7930 }
7931
7932 #[test]
7933 fn go_receiver_method_scope_chain() {
7934 let provider = TreeSitterProvider::new();
7935 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
7936
7937 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
7938
7939 assert_eq!(
7941 find("String").scope_chain,
7942 vec!["MyStruct"],
7943 "receiver method should have type in scope chain"
7944 );
7945 assert_eq!(find("String").parent.as_deref(), Some("MyStruct"));
7946
7947 assert!(
7949 find("ExportedFunction").scope_chain.is_empty(),
7950 "regular function should have empty scope chain"
7951 );
7952 }
7953
7954 #[test]
7955 fn go_ranges_valid() {
7956 let provider = TreeSitterProvider::new();
7957 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
7958
7959 for s in &symbols {
7960 assert!(
7961 s.range.end_line >= s.range.start_line,
7962 "symbol {} has invalid range: {:?}",
7963 s.name,
7964 s.range
7965 );
7966 }
7967 }
7968
7969 #[test]
7972 fn cross_language_all_six_produce_symbols() {
7973 let provider = TreeSitterProvider::new();
7974
7975 let fixtures = [
7976 ("sample.ts", "TypeScript"),
7977 ("sample.tsx", "TSX"),
7978 ("sample.js", "JavaScript"),
7979 ("sample.py", "Python"),
7980 ("sample.rs", "Rust"),
7981 ("sample.go", "Go"),
7982 ];
7983
7984 for (fixture, lang) in &fixtures {
7985 let symbols = provider
7986 .list_symbols(&fixture_path(fixture))
7987 .unwrap_or_else(|e| panic!("{} ({}) failed: {:?}", lang, fixture, e));
7988 assert!(
7989 symbols.len() >= 2,
7990 "{} should produce ≥2 symbols, got {}: {:?}",
7991 lang,
7992 symbols.len(),
7993 symbols.iter().map(|s| &s.name).collect::<Vec<_>>()
7994 );
7995 }
7996 }
7997
7998 #[test]
8001 fn symbol_cache_returns_cached_results_on_second_call() {
8002 let dir = tempfile::tempdir().unwrap();
8003 let file = dir.path().join("test.rs");
8004 std::fs::write(&file, "pub fn hello() {}\npub fn world() {}").unwrap();
8005
8006 let mut parser = FileParser::new();
8007
8008 let symbols1 = parser.extract_symbols(&file).unwrap();
8009 assert_eq!(symbols1.len(), 2);
8010
8011 let symbols2 = parser.extract_symbols(&file).unwrap();
8013 assert_eq!(symbols2.len(), 2);
8014 assert_eq!(symbols1[0].name, symbols2[0].name);
8015
8016 assert!(parser.symbol_cache.read().unwrap().contains_key(&file));
8018 }
8019
8020 #[test]
8021 fn symbol_cache_invalidates_on_file_change() {
8022 let dir = tempfile::tempdir().unwrap();
8023 let file = dir.path().join("test.rs");
8024 std::fs::write(&file, "pub fn hello() {}").unwrap();
8025
8026 let mut parser = FileParser::new();
8027
8028 let symbols1 = parser.extract_symbols(&file).unwrap();
8029 assert_eq!(symbols1.len(), 1);
8030 assert_eq!(symbols1[0].name, "hello");
8031
8032 std::thread::sleep(std::time::Duration::from_millis(50));
8034
8035 std::fs::write(&file, "pub fn hello() {}\npub fn goodbye() {}").unwrap();
8037
8038 let symbols2 = parser.extract_symbols(&file).unwrap();
8040 assert_eq!(symbols2.len(), 2);
8041 assert!(symbols2.iter().any(|s| s.name == "goodbye"));
8042 }
8043
8044 #[test]
8045 fn symbol_cache_invalidate_method_clears_entry() {
8046 let dir = tempfile::tempdir().unwrap();
8047 let file = dir.path().join("test.rs");
8048 std::fs::write(&file, "pub fn hello() {}").unwrap();
8049
8050 let mut parser = FileParser::new();
8051 parser.extract_symbols(&file).unwrap();
8052 assert!(parser.symbol_cache.read().unwrap().contains_key(&file));
8053
8054 parser.invalidate_symbols(&file);
8055 assert!(!parser.symbol_cache.read().unwrap().contains_key(&file));
8056 assert!(!parser.cache.contains_key(&file));
8058 }
8059
8060 #[test]
8061 fn symbol_cache_works_for_multiple_languages() {
8062 let dir = tempfile::tempdir().unwrap();
8063 let rs_file = dir.path().join("lib.rs");
8064 let ts_file = dir.path().join("app.ts");
8065 let py_file = dir.path().join("main.py");
8066
8067 std::fs::write(&rs_file, "pub fn rust_fn() {}").unwrap();
8068 std::fs::write(&ts_file, "export function tsFn() {}").unwrap();
8069 std::fs::write(&py_file, "def py_fn():\n pass").unwrap();
8070
8071 let mut parser = FileParser::new();
8072
8073 let rs_syms = parser.extract_symbols(&rs_file).unwrap();
8074 let ts_syms = parser.extract_symbols(&ts_file).unwrap();
8075 let py_syms = parser.extract_symbols(&py_file).unwrap();
8076
8077 assert!(rs_syms.iter().any(|s| s.name == "rust_fn"));
8078 assert!(ts_syms.iter().any(|s| s.name == "tsFn"));
8079 assert!(py_syms.iter().any(|s| s.name == "py_fn"));
8080
8081 assert_eq!(parser.symbol_cache.read().unwrap().len(), 3);
8083
8084 let rs_syms2 = parser.extract_symbols(&rs_file).unwrap();
8086 assert_eq!(rs_syms.len(), rs_syms2.len());
8087 }
8088
8089 #[test]
8090 fn extract_json_symbols_top_level_keys() {
8091 let dir = tempfile::tempdir().unwrap();
8092 let file = dir.path().join("package.json");
8093 std::fs::write(&file, r#"{"name": "x", "version": "1"}"#).unwrap();
8094
8095 let mut parser = FileParser::new();
8096 let symbols = parser.extract_symbols(&file).unwrap();
8097
8098 assert_eq!(symbols.len(), 2);
8099 assert!(symbols
8100 .iter()
8101 .any(|s| s.name == "name" && s.kind == SymbolKind::Variable));
8102 assert!(symbols
8103 .iter()
8104 .any(|s| s.name == "version" && s.kind == SymbolKind::Variable));
8105 }
8106
8107 #[test]
8108 fn extract_json_symbols_root_array() {
8109 let dir = tempfile::tempdir().unwrap();
8110 let file = dir.path().join("array.json");
8111 std::fs::write(&file, "[1,2,3]").unwrap();
8112
8113 let mut parser = FileParser::new();
8114 let symbols = parser.extract_symbols(&file).unwrap();
8115
8116 assert_eq!(symbols.len(), 0);
8117 }
8118
8119 #[test]
8120 fn extract_json_symbols_no_recursion_into_nested() {
8121 let dir = tempfile::tempdir().unwrap();
8122 let file = dir.path().join("nested.json");
8123 std::fs::write(&file, r#"{"scripts": {"build": "tsc"}}"#).unwrap();
8124
8125 let mut parser = FileParser::new();
8126 let symbols = parser.extract_symbols(&file).unwrap();
8127
8128 assert_eq!(symbols.len(), 1);
8129 assert_eq!(symbols[0].name, "scripts");
8130 assert!(!symbols.iter().any(|s| s.name == "build"));
8131 }
8132
8133 #[test]
8134 fn extract_scala_symbols_object_and_method() {
8135 let dir = tempfile::tempdir().unwrap();
8136 let file = dir.path().join("Greeter.scala");
8137 std::fs::write(
8138 &file,
8139 "object Greeter {\n def hello(name: String): String = s\"hi $name\"\n}",
8140 )
8141 .unwrap();
8142
8143 let mut parser = FileParser::new();
8144 let symbols = parser.extract_symbols(&file).unwrap();
8145
8146 assert!(symbols
8147 .iter()
8148 .any(|s| s.name == "Greeter" && s.kind == SymbolKind::Class));
8149 assert!(symbols.iter().any(|s| s.name == "hello"
8150 && s.kind == SymbolKind::Method
8151 && s.scope_chain == vec!["Greeter".to_string()]));
8152 }
8153
8154 #[test]
8155 fn extract_scala_symbols_class_and_trait() {
8156 let dir = tempfile::tempdir().unwrap();
8157 let file = dir.path().join("Types.scala");
8158 std::fs::write(&file, "class Foo\ntrait Bar").unwrap();
8159
8160 let mut parser = FileParser::new();
8161 let symbols = parser.extract_symbols(&file).unwrap();
8162
8163 assert!(symbols
8164 .iter()
8165 .any(|s| s.name == "Foo" && s.kind == SymbolKind::Class));
8166 assert!(symbols
8167 .iter()
8168 .any(|s| s.name == "Bar" && s.kind == SymbolKind::Interface));
8169 }
8170
8171 #[test]
8172 fn extract_yaml_symbols_k8s_resource() {
8173 let dir = tempfile::tempdir().unwrap();
8174 let file = dir.path().join("deployment.yaml");
8175 std::fs::write(
8176 &file,
8177 r#"apiVersion: apps/v1
8178kind: Deployment
8179metadata:
8180 name: nginx
8181 namespace: web
8182"#,
8183 )
8184 .unwrap();
8185
8186 let mut parser = FileParser::new();
8187 let symbols = parser.extract_symbols(&file).unwrap();
8188
8189 assert_eq!(symbols.len(), 1, "Expected 1 symbol for K8s Deployment");
8190 let sym = &symbols[0];
8191 assert_eq!(sym.name, "web/Deployment/nginx");
8192 assert_eq!(sym.kind, SymbolKind::Class);
8193 assert!(sym.exported);
8194 }
8195
8196 #[test]
8197 fn extract_yaml_symbols_k8s_no_namespace() {
8198 let dir = tempfile::tempdir().unwrap();
8199 let file = dir.path().join("service.yaml");
8200 std::fs::write(
8201 &file,
8202 r#"apiVersion: v1
8203kind: Service
8204metadata:
8205 name: nginx-svc
8206"#,
8207 )
8208 .unwrap();
8209
8210 let mut parser = FileParser::new();
8211 let symbols = parser.extract_symbols(&file).unwrap();
8212
8213 assert_eq!(symbols.len(), 1, "Expected 1 symbol for K8s Service");
8214 let sym = &symbols[0];
8215 assert_eq!(sym.name, "Service/nginx-svc");
8216 assert_eq!(sym.kind, SymbolKind::Class);
8217 }
8218
8219 #[test]
8220 fn extract_yaml_symbols_multidoc() {
8221 let dir = tempfile::tempdir().unwrap();
8222 let file = dir.path().join("multidoc.yaml");
8223 std::fs::write(
8224 &file,
8225 r#"apiVersion: apps/v1
8226kind: Deployment
8227metadata:
8228 name: a
8229---
8230apiVersion: v1
8231kind: Service
8232metadata:
8233 name: b
8234"#,
8235 )
8236 .unwrap();
8237
8238 let mut parser = FileParser::new();
8239 let symbols = parser.extract_symbols(&file).unwrap();
8240
8241 assert_eq!(symbols.len(), 2, "Expected 2 symbols for multi-doc YAML");
8242 assert!(symbols.iter().any(|s| s.name == "Deployment/a"));
8243 assert!(symbols.iter().any(|s| s.name == "Service/b"));
8244 }
8245
8246 #[test]
8247 fn extract_yaml_symbols_generic_fallback() {
8248 let dir = tempfile::tempdir().unwrap();
8249 let file = dir.path().join("compose.yaml");
8250 std::fs::write(
8251 &file,
8252 r#"version: "3"
8253services:
8254 web: {}
8255volumes:
8256 data: {}
8257"#,
8258 )
8259 .unwrap();
8260
8261 let mut parser = FileParser::new();
8262 let symbols = parser.extract_symbols(&file).unwrap();
8263
8264 assert_eq!(symbols.len(), 3, "Expected 3 symbols for generic YAML");
8266 assert!(symbols
8267 .iter()
8268 .any(|s| s.name == "version" && s.kind == SymbolKind::Variable));
8269 assert!(symbols
8270 .iter()
8271 .any(|s| s.name == "services" && s.kind == SymbolKind::Variable));
8272 assert!(symbols
8273 .iter()
8274 .any(|s| s.name == "volumes" && s.kind == SymbolKind::Variable));
8275 }
8276
8277 #[test]
8278 fn extract_yaml_symbols_empty() {
8279 let dir = tempfile::tempdir().unwrap();
8280 let file = dir.path().join("empty.yaml");
8281 std::fs::write(&file, "").unwrap();
8282
8283 let mut parser = FileParser::new();
8284 let symbols = parser.extract_symbols(&file).unwrap();
8285
8286 assert_eq!(symbols.len(), 0, "Expected 0 symbols for empty YAML");
8287 }
8288
8289 #[test]
8290 fn extract_yaml_symbols_resource_limits() {
8291 let dir = tempfile::tempdir().unwrap();
8292 let file = dir.path().join("deployment.yaml");
8293 std::fs::write(
8294 &file,
8295 r#"apiVersion: apps/v1
8296kind: Deployment
8297metadata:
8298 name: app
8299spec:
8300 template:
8301 spec:
8302 containers:
8303 - name: main
8304 image: myapp:1.0
8305 resources:
8306 limits:
8307 cpu: "2"
8308 memory: 1Gi
8309 requests:
8310 cpu: "1"
8311 memory: 512Mi
8312"#,
8313 )
8314 .unwrap();
8315
8316 let mut parser = FileParser::new();
8317 let symbols = parser.extract_symbols(&file).unwrap();
8318
8319 assert_eq!(symbols.len(), 1, "Expected 1 symbol for Deployment");
8320 let sym = &symbols[0];
8321 assert_eq!(sym.name, "Deployment/app");
8322 let sig = sym.signature.as_ref().unwrap();
8323 assert!(sig.contains("cpu="), "Signature should contain cpu= field");
8324 assert!(
8325 sig.contains("memory="),
8326 "Signature should contain memory= field"
8327 );
8328 }
8329
8330 #[test]
8331 fn extract_yaml_symbols_env_names() {
8332 let dir = tempfile::tempdir().unwrap();
8333 let file = dir.path().join("deployment.yaml");
8334 std::fs::write(
8335 &file,
8336 r#"apiVersion: apps/v1
8337kind: Deployment
8338metadata:
8339 name: app
8340spec:
8341 template:
8342 spec:
8343 containers:
8344 - name: main
8345 image: myapp:1.0
8346 env:
8347 - name: FOO
8348 value: "bar"
8349 - name: BAR
8350 value: "baz"
8351"#,
8352 )
8353 .unwrap();
8354
8355 let mut parser = FileParser::new();
8356 let symbols = parser.extract_symbols(&file).unwrap();
8357
8358 assert_eq!(symbols.len(), 1, "Expected 1 symbol for Deployment");
8359 let sym = &symbols[0];
8360 let sig = sym.signature.as_ref().unwrap();
8361 assert!(
8362 sig.contains("env=FOO,BAR"),
8363 "Signature should contain env=FOO,BAR"
8364 );
8365 }
8366
8367 #[test]
8368 fn extract_yaml_symbols_rbac_rules() {
8369 let dir = tempfile::tempdir().unwrap();
8370 let file = dir.path().join("role.yaml");
8371 std::fs::write(
8372 &file,
8373 r#"apiVersion: rbac.authorization.k8s.io/v1
8374kind: Role
8375metadata:
8376 name: reader
8377rules:
8378- apiGroups: [""]
8379 resources: [pods, services]
8380 verbs: [get, list, watch]
8381"#,
8382 )
8383 .unwrap();
8384
8385 let mut parser = FileParser::new();
8386 let symbols = parser.extract_symbols(&file).unwrap();
8387
8388 assert_eq!(symbols.len(), 1, "Expected 1 symbol for Role");
8389 let sym = &symbols[0];
8390 let sig = sym.signature.as_ref().unwrap();
8391 assert!(
8392 sig.contains("verbs=get,list,watch"),
8393 "Signature should contain verbs=get,list,watch"
8394 );
8395 assert!(
8396 sig.contains("resources=pods,services"),
8397 "Signature should contain resources=pods,services"
8398 );
8399 }
8400
8401 #[test]
8402 fn extract_yaml_symbols_argo_workflow() {
8403 let dir = tempfile::tempdir().unwrap();
8404 let file = dir.path().join("workflow.yaml");
8405 std::fs::write(
8406 &file,
8407 r#"apiVersion: argoproj.io/v1alpha1
8408kind: Workflow
8409metadata:
8410 name: hello-world
8411spec:
8412 entrypoint: main
8413 templates:
8414 - name: main
8415 container:
8416 image: alpine:3.18
8417 command: [echo]
8418 args: ["hello"]
8419 - name: print
8420 container:
8421 image: alpine:3.18
8422 command: [echo]
8423 args: ["world"]
8424"#,
8425 )
8426 .unwrap();
8427
8428 let mut parser = FileParser::new();
8429 let symbols = parser.extract_symbols(&file).unwrap();
8430
8431 assert_eq!(symbols.len(), 1, "Expected 1 symbol for Workflow");
8432 let sym = &symbols[0];
8433 assert!(
8434 sym.name.contains("Workflow"),
8435 "Symbol name should contain Workflow"
8436 );
8437 let sig = sym.signature.as_ref().unwrap();
8438 assert!(
8439 sig.contains("entrypoint=main"),
8440 "Signature should contain entrypoint=main"
8441 );
8442 assert!(
8443 sig.contains("templates=main,print"),
8444 "Signature should contain templates=main,print"
8445 );
8446 assert!(
8447 sig.contains("image=alpine:3.18"),
8448 "Signature should contain image=alpine:3.18"
8449 );
8450 assert!(
8451 sig.contains("command=echo"),
8452 "Signature should contain command=echo"
8453 );
8454 }
8455
8456 #[test]
8457 fn extract_yaml_symbols_generatename_fallback() {
8458 let dir = tempfile::tempdir().unwrap();
8459 let file = dir.path().join("workflow.yaml");
8460 std::fs::write(
8461 &file,
8462 r#"apiVersion: argoproj.io/v1alpha1
8463kind: Workflow
8464metadata:
8465 generateName: hello-
8466spec:
8467 entrypoint: main
8468 templates:
8469 - name: main
8470 container:
8471 image: alpine:3.18
8472"#,
8473 )
8474 .unwrap();
8475
8476 let mut parser = FileParser::new();
8477 let symbols = parser.extract_symbols(&file).unwrap();
8478
8479 assert_eq!(symbols.len(), 1, "Expected 1 symbol for Workflow");
8480 let sym = &symbols[0];
8481 assert!(
8482 sym.name.contains("hello-"),
8483 "Symbol name should contain generateName fallback 'hello-'"
8484 );
8485 }
8486
8487 #[test]
8488 fn detect_r_extensions_are_case_sensitive() {
8489 assert_eq!(detect_language(Path::new("analysis.R")), Some(LangId::R));
8490 assert_eq!(detect_language(Path::new("script.r")), Some(LangId::R));
8491 }
8492
8493 #[test]
8494 fn extract_r_symbols_test() {
8495 let dir = tempfile::tempdir().unwrap();
8496 let file = dir.path().join("analysis.R");
8497 std::fs::write(
8498 &file,
8499 r#"
8500# summary function
8501summarise <- function(data, column) {
8502 total <- sum(data[[column]])
8503 total
8504}
8505
8506normalise = function(x) {
8507 x / max(x)
8508}
8509
8510function(y) {
8511 y + 1
8512} -> transform_values
8513
8514threshold <- 10
8515"done" -> status
8516
8517outer <- function(values) {
8518 local_value <- 1
8519 local_value
8520}
8521"#,
8522 )
8523 .unwrap();
8524
8525 let mut parser = FileParser::new();
8526 let symbols = parser.extract_symbols(&file).unwrap();
8527
8528 let get = |name: &str| {
8529 symbols
8530 .iter()
8531 .find(|symbol| symbol.name == name)
8532 .unwrap_or_else(|| panic!("missing {name}; got {symbols:?}"))
8533 };
8534
8535 assert_eq!(get("summarise").kind, SymbolKind::Function);
8536 assert_eq!(get("normalise").kind, SymbolKind::Function);
8537 assert_eq!(get("transform_values").kind, SymbolKind::Function);
8538 assert!(
8539 symbols
8540 .iter()
8541 .all(|symbol| !symbol.name.starts_with("function(")),
8542 "rightward function assignments should use the RHS identifier: {symbols:?}"
8543 );
8544 assert_eq!(get("outer").kind, SymbolKind::Function);
8545 assert_eq!(get("threshold").kind, SymbolKind::Variable);
8546 assert_eq!(get("status").kind, SymbolKind::Variable);
8547 assert!(
8548 symbols.iter().all(|symbol| symbol.name != "local_value"),
8549 "nested assignments should not surface as top-level variables: {symbols:?}"
8550 );
8551 }
8552
8553 #[test]
8554 fn extract_pascal_symbols_test() {
8555 let dir = tempfile::tempdir().unwrap();
8556 let file = dir.path().join("MyUnit.pas");
8557 std::fs::write(
8558 &file,
8559 r#"
8560unit MyUnit;
8561
8562interface
8563
8564uses SysUtils;
8565
8566type
8567 TMyClass = class
8568 private
8569 FValue: Integer;
8570 public
8571 constructor Create;
8572 procedure DoSomething; virtual;
8573 end;
8574
8575 TMyRecord = record
8576 X, Y: Integer;
8577 end;
8578
8579 IMyInterface = interface
8580 procedure DoSomethingElse;
8581 end;
8582
8583 TMyEnum = (Red, Green, Blue);
8584
8585const
8586 UNIT_CONST = 42;
8587
8588var
8589 UnitVar: string;
8590
8591implementation
8592
8593constructor TMyClass.Create;
8594begin
8595 FValue := 0;
8596end;
8597
8598procedure TMyClass.DoSomething;
8599begin
8600end;
8601
8602procedure StandaloneProc;
8603begin
8604end;
8605
8606end.
8607"#,
8608 )
8609 .unwrap();
8610
8611 let mut parser = FileParser::new();
8612 let symbols = parser.extract_symbols(&file).unwrap();
8613
8614 let get = |name: &str| {
8615 symbols
8616 .iter()
8617 .find(|symbol| symbol.name == name)
8618 .unwrap_or_else(|| panic!("missing {name}; got {symbols:?}"))
8619 };
8620
8621 assert_eq!(get("MyUnit").kind, SymbolKind::Class);
8622 assert_eq!(get("TMyClass").kind, SymbolKind::Class);
8623 assert_eq!(get("TMyRecord").kind, SymbolKind::Struct);
8624 assert_eq!(get("IMyInterface").kind, SymbolKind::Interface);
8625 assert_eq!(get("TMyEnum").kind, SymbolKind::Enum);
8626 assert_eq!(get("UNIT_CONST").kind, SymbolKind::Variable);
8627 assert_eq!(get("UnitVar").kind, SymbolKind::Variable);
8628 assert_eq!(get("StandaloneProc").kind, SymbolKind::Function);
8629
8630 let create_methods: Vec<&Symbol> = symbols.iter().filter(|s| s.name == "Create").collect();
8632 assert_eq!(create_methods.len(), 2); for m in create_methods {
8634 assert_eq!(m.kind, SymbolKind::Method);
8635 assert_eq!(m.scope_chain, vec!["TMyClass".to_string()]);
8636 }
8637
8638 let do_something_methods: Vec<&Symbol> =
8639 symbols.iter().filter(|s| s.name == "DoSomething").collect();
8640 assert_eq!(do_something_methods.len(), 2); for m in do_something_methods {
8642 assert_eq!(m.kind, SymbolKind::Method);
8643 assert_eq!(m.scope_chain, vec!["TMyClass".to_string()]);
8644 }
8645 }
8646}