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