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