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