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