1use std::cell::RefCell;
2use std::collections::{BTreeMap, 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
140#[cfg(test)]
141const RS_QUERY: &str = r#"
142;; free functions (with optional visibility)
143(function_item
144 name: (identifier) @fn.name) @fn.def
145
146;; struct items
147(struct_item
148 name: (type_identifier) @struct.name) @struct.def
149
150;; enum items
151(enum_item
152 name: (type_identifier) @enum.name) @enum.def
153
154;; trait items
155(trait_item
156 name: (type_identifier) @trait.name) @trait.def
157
158;; impl blocks — capture the whole block to find methods
159(impl_item) @impl.def
160
161;; visibility modifiers on any item
162(visibility_modifier) @vis.mod
163"#;
164
165const GO_QUERY: &str = r#"
166;; function declarations
167(function_declaration
168 name: (identifier) @fn.name) @fn.def
169
170;; method declarations (with receiver)
171(method_declaration
172 name: (field_identifier) @method.name) @method.def
173
174;; type declarations (struct and interface)
175(type_declaration
176 (type_spec
177 name: (type_identifier) @type.name
178 type: (_) @type.body)) @type.def
179"#;
180
181const C_QUERY: &str = r#"
182;; function definitions
183(function_definition
184 declarator: (function_declarator
185 declarator: (identifier) @fn.name)) @fn.def
186
187;; function declarations / prototypes
188(declaration
189 declarator: (function_declarator
190 declarator: (identifier) @fn.name)) @fn.def
191
192;; struct declarations
193(struct_specifier
194 name: (type_identifier) @struct.name
195 body: (field_declaration_list)) @struct.def
196
197;; enum declarations
198(enum_specifier
199 name: (type_identifier) @enum.name
200 body: (enumerator_list)) @enum.def
201
202;; typedef aliases
203(type_definition
204 declarator: (type_identifier) @type.name) @type.def
205
206;; macros
207(preproc_def
208 name: (identifier) @macro.name) @macro.def
209
210(preproc_function_def
211 name: (identifier) @macro.name) @macro.def
212"#;
213
214const CPP_QUERY: &str = r#"
215;; free function definitions
216(function_definition
217 declarator: (function_declarator
218 declarator: (identifier) @fn.name)) @fn.def
219
220;; free function declarations
221(declaration
222 declarator: (function_declarator
223 declarator: (identifier) @fn.name)) @fn.def
224
225;; inline method definitions / declarations inside class bodies
226(function_definition
227 declarator: (function_declarator
228 declarator: (field_identifier) @method.name)) @method.def
229
230(field_declaration
231 declarator: (function_declarator
232 declarator: (field_identifier) @method.name)) @method.def
233
234;; qualified functions / methods
235(function_definition
236 declarator: (function_declarator
237 declarator: (qualified_identifier
238 scope: (_) @qual.scope
239 name: (identifier) @qual.name))) @qual.def
240
241(declaration
242 declarator: (function_declarator
243 declarator: (qualified_identifier
244 scope: (_) @qual.scope
245 name: (identifier) @qual.name))) @qual.def
246
247;; class / struct / enum / namespace declarations
248(class_specifier
249 name: (_) @class.name) @class.def
250
251(struct_specifier
252 name: (_) @struct.name) @struct.def
253
254(enum_specifier
255 name: (_) @enum.name) @enum.def
256
257(namespace_definition
258 name: (_) @namespace.name) @namespace.def
259
260;; template declarations
261(template_declaration
262 (class_specifier
263 name: (_) @template.class.name) @template.class.item) @template.class.def
264
265(template_declaration
266 (struct_specifier
267 name: (_) @template.struct.name) @template.struct.item) @template.struct.def
268
269(template_declaration
270 (function_definition
271 declarator: (function_declarator
272 declarator: (identifier) @template.fn.name)) @template.fn.item) @template.fn.def
273
274(template_declaration
275 (function_definition
276 declarator: (function_declarator
277 declarator: (qualified_identifier
278 scope: (_) @template.qual.scope
279 name: (identifier) @template.qual.name))) @template.qual.item) @template.qual.def
280"#;
281
282const ZIG_QUERY: &str = r#"
283;; functions
284(function_declaration
285 name: (identifier) @fn.name) @fn.def
286
287;; container declarations bound to const names
288(variable_declaration
289 (identifier) @struct.name
290 "="
291 (struct_declaration) @struct.body) @struct.def
292
293(variable_declaration
294 (identifier) @enum.name
295 "="
296 (enum_declaration) @enum.body) @enum.def
297
298(variable_declaration
299 (identifier) @union.name
300 "="
301 (union_declaration) @union.body) @union.def
302
303;; const declarations
304(variable_declaration
305 (identifier) @const.name) @const.def
306
307;; tests
308(test_declaration
309 (string) @test.name) @test.def
310
311(test_declaration
312 (identifier) @test.name) @test.def
313"#;
314
315const CSHARP_QUERY: &str = r#"
316;; types
317(class_declaration
318 name: (identifier) @class.name) @class.def
319
320(interface_declaration
321 name: (identifier) @interface.name) @interface.def
322
323(struct_declaration
324 name: (identifier) @struct.name) @struct.def
325
326(enum_declaration
327 name: (identifier) @enum.name) @enum.def
328
329;; members
330(method_declaration
331 name: (identifier) @method.name) @method.def
332
333(property_declaration
334 name: (identifier) @property.name) @property.def
335
336;; namespaces
337(namespace_declaration
338 name: (_) @namespace.name) @namespace.def
339
340(file_scoped_namespace_declaration
341 name: (_) @namespace.name) @namespace.def
342"#;
343
344const BASH_QUERY: &str = r#"
347;; function definitions (both `function foo()` and `foo()` styles)
348(function_definition
349 name: (word) @fn.name) @fn.def
350"#;
351
352const SOL_QUERY: &str = r#"
355;; contracts / libraries / interfaces
356(contract_declaration
357 name: (identifier) @contract.name) @contract.def
358
359(library_declaration
360 name: (identifier) @library.name) @library.def
361
362(interface_declaration
363 name: (identifier) @interface.name) @interface.def
364
365;; functions, modifiers, constructors
366(function_definition
367 name: (identifier) @fn.name) @fn.def
368
369(modifier_definition
370 name: (identifier) @modifier.name) @modifier.def
371
372(constructor_definition) @constructor.def
373
374(fallback_receive_definition) @fallback_receive.def
375
376;; events / errors
377(event_definition
378 name: (identifier) @event.name) @event.def
379
380(error_declaration
381 name: (identifier) @error.name) @error.def
382
383;; data types
384(struct_declaration
385 name: (identifier) @struct.name) @struct.def
386
387(enum_declaration
388 name: (identifier) @enum.name) @enum.def
389
390;; state variables (top-level inside a contract)
391(state_variable_declaration
392 name: (identifier) @var.name) @var.def
393"#;
394
395const PASCAL_QUERY: &str = r#"
396;; program / unit
397(program (moduleName (identifier) @program.name)) @program.def
398(unit (moduleName (identifier) @unit.name)) @unit.def
399
400;; type declarations
401(declType (identifier) @type.name) @type.def
402
403;; const / var declarations
404(declConst (identifier) @const.name) @const.def
405(declVar (identifier) @var.name) @var.def
406
407;; procedure / function definitions (implementation)
408(defProc
409 (declProc
410 [
411 (identifier) @proc.name
412 (genericDot) @proc.name
413 ])) @proc.def
414
415;; procedure / function declarations (interface / class)
416(declProc
417 [
418 (identifier) @proc.name
419 (genericDot) @proc.name
420 ]) @proc.def
421"#;
422
423const R_QUERY: &str = r#"
424;; R represents assignments as binary operators. The extractor filters these
425;; broad captures down to top-level assignment operators and classifies
426;; function-valued assignments as functions. Rightward function
427;; assignment parses as a function_definition whose body is a binary_operator.
428(binary_operator) @assign.def
429(function_definition) @function.def
430"#;
431
432const GROOVY_QUERY: &str = r#"
433;; types
434(class_declaration
435 name: (identifier) @class.name) @class.def
436(interface_declaration
437 name: (identifier) @interface.name) @interface.def
438(trait_declaration
439 name: (identifier) @trait.name) @trait.def
440(enum_declaration
441 name: (identifier) @enum.name) @enum.def
442
443;; methods and top-level script functions
444(method_declaration
445 name: [(identifier) (quoted_identifier)] @fn.name) @fn.def
446
447;; fields and properties
448(field_declaration
449 (variable_declarator
450 name: (identifier) @var.name)) @var.def
451
452;; Jenkins declarative pipeline root
453(pipeline_statement) @pipeline.def
454"#;
455
456const OBJC_QUERY: &str = r#"
457;; Objective-C class and protocol containers. The extractor derives names from
458;; the first identifier child because the grammar does not field-name them.
459(class_interface) @class.def
460(class_implementation) @class.def
461(protocol_declaration) @interface.def
462
463;; Method bodies, property declarations, C functions, and typedefs in .m/.mm files.
464(method_definition) @method.def
465(property_declaration) @property.def
466(function_definition) @fn.def
467(type_definition) @type.def
468"#;
469
470const SCSS_QUERY: &str = r#"
471;; SCSS definitions
472(mixin_statement
473 name: (identifier) @mixin.name) @mixin.def
474
475(function_statement
476 name: (identifier) @fn.name) @fn.def
477
478(declaration
479 (property_name) @var.name) @var.def
480
481(rule_set
482 (selectors) @selector.name) @selector.def
483"#;
484
485const SCALA_QUERY: &str = r#"
486;; classes / objects / traits
487(class_definition
488 name: (identifier) @class.name) @class.def
489(object_definition
490 name: (identifier) @object.name) @object.def
491(enum_definition
492 name: (_) @enum.name) @enum.def
493(trait_definition
494 name: (identifier) @trait.name) @trait.def
495;; methods (def)
496(function_definition
497 name: (identifier) @fn.name) @fn.def
498(function_declaration
499 name: (identifier) @fn.name) @fn.def
500;; vals / vars / type aliases
501(val_definition
502 pattern: (identifier) @val.name) @val.def
503(var_definition
504 pattern: (identifier) @var.name) @var.def
505(given_definition
506 name: (_) @given.name) @given.def
507(type_definition
508 name: (type_identifier) @type.name) @type.def
509"#;
510
511const JAVA_QUERY: &str = r#"
512;; types
513(class_declaration
514 name: (identifier) @class.name) @class.def
515(interface_declaration
516 name: (identifier) @interface.name) @interface.def
517(annotation_type_declaration
518 name: (identifier) @interface.name) @interface.def
519(enum_declaration
520 name: (identifier) @enum.name) @enum.def
521(record_declaration
522 name: (identifier) @struct.name) @struct.def
523
524;; members
525(method_declaration
526 name: (identifier) @fn.name) @fn.def
527(constructor_declaration
528 name: (identifier) @fn.name) @fn.def
529(field_declaration
530 declarator: (variable_declarator
531 name: (identifier) @var.name)) @var.def
532"#;
533
534const RUBY_QUERY: &str = r#"
535;; modules / classes
536(module
537 name: (constant) @module.name) @module.def
538(class
539 name: (constant) @class.name) @class.def
540
541;; methods
542(method
543 name: (_) @fn.name) @fn.def
544(singleton_method
545 name: (_) @fn.name) @fn.def
546
547;; constants
548(assignment
549 left: (constant) @var.name) @var.def
550"#;
551
552const KOTLIN_QUERY: &str = r#"
553;; declarations
554(class_declaration
555 (type_identifier) @class.name) @class.def
556(object_declaration
557 (type_identifier) @object.name) @object.def
558(function_declaration
559 (simple_identifier) @fn.name) @fn.def
560(property_declaration
561 (variable_declaration
562 (simple_identifier) @var.name)) @var.def
563(type_alias
564 (type_identifier) @type.name) @type.def
565"#;
566
567const SWIFT_QUERY: &str = r#"
568;; types
569(class_declaration
570 name: (type_identifier) @class.name) @class.def
571(protocol_declaration
572 name: (type_identifier) @interface.name) @interface.def
573
574;; functions and members
575(function_declaration
576 name: (simple_identifier) @fn.name) @fn.def
577(protocol_function_declaration
578 name: (simple_identifier) @fn.name) @fn.def
579(property_declaration
580 name: (pattern
581 bound_identifier: (simple_identifier) @var.name)) @var.def
582(typealias_declaration
583 name: (type_identifier) @type.name) @type.def
584"#;
585
586const PHP_QUERY: &str = r#"
587;; namespaces and types
588(namespace_definition
589 name: (namespace_name) @namespace.name) @namespace.def
590(class_declaration
591 name: (name) @class.name) @class.def
592(interface_declaration
593 name: (name) @interface.name) @interface.def
594(trait_declaration
595 name: (name) @trait.name) @trait.def
596(enum_declaration
597 name: (name) @enum.name) @enum.def
598
599;; functions and members
600(function_definition
601 name: (name) @fn.name) @fn.def
602(method_declaration
603 name: (name) @fn.name) @fn.def
604(property_declaration
605 (property_element
606 name: (variable_name (name) @var.name))) @var.def
607"#;
608
609const LUA_QUERY: &str = r#"
610;; functions
611(function_declaration
612 name: (identifier) @fn.name) @fn.def
613(function_declaration
614 name: (dot_index_expression
615 field: (identifier) @fn.name)) @fn.def
616(function_declaration
617 name: (method_index_expression
618 method: (identifier) @fn.name)) @fn.def
619
620;; locals / module tables
621(variable_declaration
622 (assignment_statement
623 (variable_list
624 name: (identifier) @var.name))) @var.def
625(variable_declaration
626 (assignment_statement
627 (variable_list
628 name: (variable) @var.name))) @var.def
629(variable_declaration
630 (variable_list
631 name: (identifier) @var.name)) @var.def
632(variable_declaration
633 (variable_list
634 name: (variable) @var.name)) @var.def
635"#;
636
637const PERL_QUERY: &str = r#"
638;; packages and subroutines
639(package_statement
640 name: (package) @package.name) @package.def
641(subroutine_declaration_statement
642 name: (bareword) @fn.name) @fn.def
643(method_declaration_statement
644 name: (bareword) @fn.name) @fn.def
645
646;; constants: `use constant NAME => ...;` — the `constant` pragma is filtered in
647;; Rust (the tree-sitter binding does not evaluate `#eq?` predicates).
648(use_statement
649 module: (package) @const.pragma
650 (list_expression (autoquoted_bareword) @const.name)) @const.def
651
652;; lexical / package variables: capture the sigil-bearing variable node so the
653;; symbol name matches `$counter`, not the bare `counter`.
654(variable_declaration
655 variable: (_) @var.name) @var.def
656"#;
657
658#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
660pub enum LangId {
661 TypeScript,
662 Tsx,
663 JavaScript,
664 Python,
665 Rust,
666 Go,
667 C,
668 Cpp,
669 Cuda,
670 Metal,
671 Zig,
672 CSharp,
673 Bash,
674 Html,
675 Markdown,
676 Solidity,
677 Scss,
678 Vue,
679 Json,
680 Scala,
681 Java,
682 Ruby,
683 Kotlin,
684 Swift,
685 Php,
686 Lua,
687 Perl,
688 Yaml,
689 Pascal,
690 R,
691 Groovy,
692 ObjC,
693 Toml,
694}
695
696pub fn detect_language(path: &Path) -> Option<LangId> {
698 if path.file_name().and_then(|name| name.to_str()) == Some("Jenkinsfile") {
699 return Some(LangId::Groovy);
700 }
701
702 let ext = path.extension()?.to_str()?;
703 match ext {
704 "ts" | "mts" | "cts" => Some(LangId::TypeScript),
705 "tsx" => Some(LangId::Tsx),
706 "js" | "jsx" | "mjs" | "cjs" => Some(LangId::JavaScript),
707 "py" | "pyi" => Some(LangId::Python),
708 "rs" => Some(LangId::Rust),
709 "go" => Some(LangId::Go),
710 "c" | "h" => Some(LangId::C),
711 "cc" | "cpp" | "cxx" | "hpp" | "hh" => Some(LangId::Cpp),
712 "cu" | "cuh" => Some(LangId::Cuda),
713 "metal" => Some(LangId::Metal),
714 "zig" => Some(LangId::Zig),
715 "cs" => Some(LangId::CSharp),
716 "sh" | "bash" | "zsh" => Some(LangId::Bash),
717 "html" | "htm" => Some(LangId::Html),
718 "md" | "markdown" | "mdx" | "qmd" | "Qmd" | "rmd" | "Rmd" => Some(LangId::Markdown),
719 "sol" => Some(LangId::Solidity),
720 "scss" => Some(LangId::Scss),
721 "vue" => Some(LangId::Vue),
722 "json" | "jsonc" => Some(LangId::Json),
723 "scala" | "sc" => Some(LangId::Scala),
724 "java" => Some(LangId::Java),
725 "rb" => Some(LangId::Ruby),
726 "kt" | "kts" => Some(LangId::Kotlin),
727 "swift" => Some(LangId::Swift),
728 "inc" | "php" => Some(LangId::Php),
729 "lua" => Some(LangId::Lua),
730 "pl" | "pm" | "t" => Some(LangId::Perl),
731 "yaml" | "yml" => Some(LangId::Yaml),
732 "pas" | "pp" | "dpr" | "dpk" | "lpr" => Some(LangId::Pascal),
733 "R" | "r" => Some(LangId::R),
734 "groovy" | "gvy" | "gy" | "gsh" | "gradle" => Some(LangId::Groovy),
735 "m" | "mm" => Some(LangId::ObjC),
736 "toml" => Some(LangId::Toml),
737 _ => None,
738 }
739}
740
741pub fn grammar_for(lang: LangId) -> Language {
743 match lang {
744 LangId::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
745 LangId::Tsx => tree_sitter_typescript::LANGUAGE_TSX.into(),
746 LangId::JavaScript => tree_sitter_javascript::LANGUAGE.into(),
747 LangId::Python => tree_sitter_python::LANGUAGE.into(),
748 LangId::Rust => tree_sitter_rust::LANGUAGE.into(),
749 LangId::Go => tree_sitter_go::LANGUAGE.into(),
750 LangId::C => tree_sitter_c::LANGUAGE.into(),
751 LangId::Cpp => tree_sitter_cpp::LANGUAGE.into(),
752 LangId::Cuda => tree_sitter_cuda::LANGUAGE.into(),
753 LangId::Metal => tree_sitter_cpp::LANGUAGE.into(),
754 LangId::Zig => tree_sitter_zig::LANGUAGE.into(),
755 LangId::CSharp => tree_sitter_c_sharp::LANGUAGE.into(),
756 LangId::Bash => tree_sitter_bash::LANGUAGE.into(),
757 LangId::Html => tree_sitter_html::LANGUAGE.into(),
758 LangId::Markdown => tree_sitter_md::LANGUAGE.into(),
759 LangId::Solidity => tree_sitter_solidity::LANGUAGE.into(),
760 LangId::Scss => tree_sitter_scss::language(),
761 LangId::Vue => tree_sitter_vue::LANGUAGE.into(),
762 LangId::Json => tree_sitter_json::LANGUAGE.into(),
763 LangId::Scala => tree_sitter_scala::LANGUAGE.into(),
764 LangId::Java => tree_sitter_java::LANGUAGE.into(),
765 LangId::Ruby => tree_sitter_ruby::LANGUAGE.into(),
766 LangId::Kotlin => tree_sitter_kotlin_sg::LANGUAGE.into(),
767 LangId::Swift => tree_sitter_swift::LANGUAGE.into(),
768 LangId::Php => tree_sitter_php::LANGUAGE_PHP.into(),
769 LangId::Lua => tree_sitter_lua::LANGUAGE.into(),
770 LangId::Perl => tree_sitter_perl::LANGUAGE.into(),
771 LangId::Yaml => tree_sitter_yaml::LANGUAGE.into(),
772 LangId::Pascal => tree_sitter_pascal::LANGUAGE.into(),
773 LangId::R => tree_sitter_r::LANGUAGE.into(),
774 LangId::Groovy => dekobon_tree_sitter_groovy::LANGUAGE.into(),
775 LangId::ObjC => tree_sitter_objc::LANGUAGE.into(),
776 LangId::Toml => tree_sitter_toml::LANGUAGE.into(),
777 }
778}
779
780fn query_for(lang: LangId) -> Option<&'static str> {
782 match lang {
783 LangId::TypeScript | LangId::Tsx => Some(TS_QUERY),
784 LangId::JavaScript => Some(JS_QUERY),
785 LangId::Python => Some(PY_QUERY),
786 LangId::Rust => None,
787 LangId::Go => Some(GO_QUERY),
788 LangId::C => Some(C_QUERY),
789 LangId::Cpp | LangId::Cuda | LangId::Metal => Some(CPP_QUERY),
790 LangId::Zig => Some(ZIG_QUERY),
791 LangId::CSharp => Some(CSHARP_QUERY),
792 LangId::Bash => Some(BASH_QUERY),
793 LangId::Html => None, LangId::Markdown => None,
795 LangId::Solidity => Some(SOL_QUERY),
796 LangId::Scss => Some(SCSS_QUERY),
797 LangId::Vue => None,
798 LangId::Json => None,
799 LangId::Scala => Some(SCALA_QUERY),
800 LangId::Java => Some(JAVA_QUERY),
801 LangId::Ruby => Some(RUBY_QUERY),
802 LangId::Kotlin => Some(KOTLIN_QUERY),
803 LangId::Swift => Some(SWIFT_QUERY),
804 LangId::Php => Some(PHP_QUERY),
805 LangId::Lua => Some(LUA_QUERY),
806 LangId::Perl => Some(PERL_QUERY),
807 LangId::Yaml => None, LangId::Pascal => Some(PASCAL_QUERY),
809 LangId::R => Some(R_QUERY),
810 LangId::Groovy => Some(GROOVY_QUERY),
811 LangId::ObjC => Some(OBJC_QUERY),
812 LangId::Toml => None,
813 }
814}
815
816static TS_QUERY_CACHE: LazyLock<Result<Query, String>> =
817 LazyLock::new(|| compile_query(LangId::TypeScript));
818static TSX_QUERY_CACHE: LazyLock<Result<Query, String>> =
819 LazyLock::new(|| compile_query(LangId::Tsx));
820static JS_QUERY_CACHE: LazyLock<Result<Query, String>> =
821 LazyLock::new(|| compile_query(LangId::JavaScript));
822static PY_QUERY_CACHE: LazyLock<Result<Query, String>> =
823 LazyLock::new(|| compile_query(LangId::Python));
824static GO_QUERY_CACHE: LazyLock<Result<Query, String>> =
825 LazyLock::new(|| compile_query(LangId::Go));
826static C_QUERY_CACHE: LazyLock<Result<Query, String>> = LazyLock::new(|| compile_query(LangId::C));
827static CPP_QUERY_CACHE: LazyLock<Result<Query, String>> =
828 LazyLock::new(|| compile_query(LangId::Cpp));
829static CUDA_QUERY_CACHE: LazyLock<Result<Query, String>> =
830 LazyLock::new(|| compile_query(LangId::Cuda));
831static ZIG_QUERY_CACHE: LazyLock<Result<Query, String>> =
832 LazyLock::new(|| compile_query(LangId::Zig));
833static CSHARP_QUERY_CACHE: LazyLock<Result<Query, String>> =
834 LazyLock::new(|| compile_query(LangId::CSharp));
835static BASH_QUERY_CACHE: LazyLock<Result<Query, String>> =
836 LazyLock::new(|| compile_query(LangId::Bash));
837static SOL_QUERY_CACHE: LazyLock<Result<Query, String>> =
838 LazyLock::new(|| compile_query(LangId::Solidity));
839static SCSS_QUERY_CACHE: LazyLock<Result<Query, String>> =
840 LazyLock::new(|| compile_query(LangId::Scss));
841static SCALA_QUERY_CACHE: LazyLock<Result<Query, String>> =
842 LazyLock::new(|| compile_query(LangId::Scala));
843static JAVA_QUERY_CACHE: LazyLock<Result<Query, String>> =
844 LazyLock::new(|| compile_query(LangId::Java));
845static RUBY_QUERY_CACHE: LazyLock<Result<Query, String>> =
846 LazyLock::new(|| compile_query(LangId::Ruby));
847static KOTLIN_QUERY_CACHE: LazyLock<Result<Query, String>> =
848 LazyLock::new(|| compile_query(LangId::Kotlin));
849static SWIFT_QUERY_CACHE: LazyLock<Result<Query, String>> =
850 LazyLock::new(|| compile_query(LangId::Swift));
851static PHP_QUERY_CACHE: LazyLock<Result<Query, String>> =
852 LazyLock::new(|| compile_query(LangId::Php));
853static LUA_QUERY_CACHE: LazyLock<Result<Query, String>> =
854 LazyLock::new(|| compile_query(LangId::Lua));
855static PERL_QUERY_CACHE: LazyLock<Result<Query, String>> =
856 LazyLock::new(|| compile_query(LangId::Perl));
857static PASCAL_QUERY_CACHE: LazyLock<Result<Query, String>> =
858 LazyLock::new(|| compile_query(LangId::Pascal));
859static R_QUERY_CACHE: LazyLock<Result<Query, String>> = LazyLock::new(|| compile_query(LangId::R));
860static GROOVY_QUERY_CACHE: LazyLock<Result<Query, String>> =
861 LazyLock::new(|| compile_query(LangId::Groovy));
862static OBJC_QUERY_CACHE: LazyLock<Result<Query, String>> =
863 LazyLock::new(|| compile_query(LangId::ObjC));
864
865fn compile_query(lang: LangId) -> Result<Query, String> {
866 let query_src = query_for(lang).ok_or_else(|| format!("missing query for {lang:?}"))?;
867 let grammar = grammar_for(lang);
868 Query::new(&grammar, query_src)
869 .map_err(|error| format!("query compile error for {lang:?}: {error}"))
870}
871
872fn cached_query_for(lang: LangId) -> Result<Option<&'static Query>, AftError> {
873 let query = match lang {
874 LangId::TypeScript => Some(&*TS_QUERY_CACHE),
875 LangId::Tsx => Some(&*TSX_QUERY_CACHE),
876 LangId::JavaScript => Some(&*JS_QUERY_CACHE),
877 LangId::Python => Some(&*PY_QUERY_CACHE),
878 LangId::Go => Some(&*GO_QUERY_CACHE),
879 LangId::C => Some(&*C_QUERY_CACHE),
880 LangId::Cpp => Some(&*CPP_QUERY_CACHE),
881 LangId::Cuda => Some(&*CUDA_QUERY_CACHE),
882 LangId::Metal => Some(&*CPP_QUERY_CACHE),
883 LangId::Zig => Some(&*ZIG_QUERY_CACHE),
884 LangId::CSharp => Some(&*CSHARP_QUERY_CACHE),
885 LangId::Bash => Some(&*BASH_QUERY_CACHE),
886 LangId::Solidity => Some(&*SOL_QUERY_CACHE),
887 LangId::Scss => Some(&*SCSS_QUERY_CACHE),
888 LangId::Scala => Some(&*SCALA_QUERY_CACHE),
889 LangId::Java => Some(&*JAVA_QUERY_CACHE),
890 LangId::Ruby => Some(&*RUBY_QUERY_CACHE),
891 LangId::Kotlin => Some(&*KOTLIN_QUERY_CACHE),
892 LangId::Swift => Some(&*SWIFT_QUERY_CACHE),
893 LangId::Php => Some(&*PHP_QUERY_CACHE),
894 LangId::Lua => Some(&*LUA_QUERY_CACHE),
895 LangId::Perl => Some(&*PERL_QUERY_CACHE),
896 LangId::Pascal => Some(&*PASCAL_QUERY_CACHE),
897 LangId::R => Some(&*R_QUERY_CACHE),
898 LangId::Groovy => Some(&*GROOVY_QUERY_CACHE),
899 LangId::ObjC => Some(&*OBJC_QUERY_CACHE),
900 LangId::Rust
901 | LangId::Html
902 | LangId::Markdown
903 | LangId::Vue
904 | LangId::Json
905 | LangId::Yaml
906 | LangId::Toml => None,
907 };
908
909 query
910 .map(|result| {
911 result.as_ref().map_err(|message| AftError::ParseError {
912 message: message.clone(),
913 })
914 })
915 .transpose()
916}
917
918thread_local! {
919 static REUSABLE_PARSERS: RefCell<HashMap<LangId, Parser>> = RefCell::new(HashMap::new());
922}
923
924pub(crate) fn parse_source_with_cached_parser(
926 path: &Path,
927 source: &str,
928 lang: LangId,
929) -> Result<Tree, AftError> {
930 REUSABLE_PARSERS.with(|parsers| {
931 let mut parsers = parsers.borrow_mut();
932 if let std::collections::hash_map::Entry::Vacant(entry) = parsers.entry(lang) {
933 let grammar = grammar_for(lang);
934 let mut parser = Parser::new();
935 parser.set_language(&grammar).map_err(|error| {
936 crate::slog_error!("grammar init failed for {:?}: {}", lang, error);
937 AftError::ParseError {
938 message: format!("grammar init failed for {:?}: {}", lang, error),
939 }
940 })?;
941 entry.insert(parser);
942 }
943
944 parsers
945 .get_mut(&lang)
946 .expect("parser inserted for language")
947 .parse(source, None)
948 .ok_or_else(|| AftError::ParseError {
949 message: format!("tree-sitter parse returned None for {}", path.display()),
950 })
951 })
952}
953
954struct CachedTree {
956 mtime: SystemTime,
957 size: u64,
958 content_hash: blake3::Hash,
959 tree: Tree,
960}
961
962#[derive(Clone, PartialEq, Eq)]
964struct CachedSymbols {
965 mtime: SystemTime,
966 size: u64,
967 content_hash: blake3::Hash,
968 symbols: Vec<Symbol>,
969}
970
971fn content_hash_for_source(source: &str) -> blake3::Hash {
972 if source.len() as u64 > cache_freshness::CONTENT_HASH_SIZE_CAP {
973 cache_freshness::zero_hash()
974 } else {
975 cache_freshness::hash_bytes(source.as_bytes())
976 }
977}
978
979fn cached_file_is_fresh(
980 path: &Path,
981 cached_mtime: SystemTime,
982 cached_size: u64,
983 cached_content_hash: blake3::Hash,
984 fallback_mtime: SystemTime,
985) -> bool {
986 let Ok(metadata) = std::fs::metadata(path) else {
987 return false;
988 };
989 let current_size = metadata.len();
990 if current_size != cached_size {
991 return false;
992 }
993
994 let current_mtime = metadata.modified().unwrap_or(fallback_mtime);
995 if current_mtime == cached_mtime {
998 return true;
999 }
1000 if current_size > cache_freshness::CONTENT_HASH_SIZE_CAP {
1001 return false;
1002 }
1003
1004 matches!(
1005 cache_freshness::hash_file_if_small(path, current_size),
1006 Ok(Some(hash)) if hash == cached_content_hash
1007 )
1008}
1009
1010#[derive(Clone, Default)]
1013pub struct SymbolCache {
1014 entries: HashMap<PathBuf, CachedSymbols>,
1015 generation: u64,
1016 mutation_revision: u64,
1018 persisted_revision: u64,
1019 project_root: Option<PathBuf>,
1020}
1021
1022pub type SharedSymbolCache = Arc<RwLock<SymbolCache>>;
1023
1024#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1025pub(crate) struct SymbolCacheLoadOutcome {
1026 pub(crate) loaded: usize,
1027 pub(crate) needs_persistence: bool,
1028}
1029
1030fn symbols_estimated_bytes(symbols: &[Symbol]) -> u64 {
1031 symbols.iter().fold(0u64, |bytes, symbol| {
1032 let scope_bytes = symbol.scope_chain.iter().fold(0u64, |scope_bytes, scope| {
1033 scope_bytes
1034 .saturating_add(std::mem::size_of::<String>() as u64)
1035 .saturating_add(crate::memory::usize_to_u64(scope.len()))
1036 });
1037 bytes
1038 .saturating_add(std::mem::size_of::<Symbol>() as u64)
1039 .saturating_add(crate::memory::usize_to_u64(symbol.name.len()))
1040 .saturating_add(
1041 symbol
1042 .signature
1043 .as_ref()
1044 .map(|signature| crate::memory::usize_to_u64(signature.len()))
1045 .unwrap_or(0),
1046 )
1047 .saturating_add(scope_bytes)
1048 .saturating_add(
1049 symbol
1050 .parent
1051 .as_ref()
1052 .map(|parent| crate::memory::usize_to_u64(parent.len()))
1053 .unwrap_or(0),
1054 )
1055 })
1056}
1057
1058impl SymbolCache {
1059 pub fn new() -> Self {
1060 Self {
1061 entries: HashMap::new(),
1062 generation: 0,
1063 mutation_revision: 0,
1064 persisted_revision: 0,
1065 project_root: None,
1066 }
1067 }
1068
1069 pub fn set_project_root(&mut self, project_root: PathBuf) {
1071 debug_assert!(project_root.is_absolute());
1072 self.project_root = Some(project_root);
1073 }
1074
1075 pub fn set_project_root_for_generation(
1078 &mut self,
1079 generation: u64,
1080 project_root: PathBuf,
1081 ) -> bool {
1082 if self.generation != generation {
1083 return false;
1084 }
1085 self.set_project_root(project_root);
1086 true
1087 }
1088
1089 pub fn insert(
1091 &mut self,
1092 path: PathBuf,
1093 mtime: SystemTime,
1094 size: u64,
1095 content_hash: blake3::Hash,
1096 symbols: Vec<Symbol>,
1097 ) -> bool {
1098 let entry = CachedSymbols {
1099 mtime,
1100 size,
1101 content_hash,
1102 symbols,
1103 };
1104 if self.entries.get(&path) == Some(&entry) {
1105 return false;
1106 }
1107 self.entries.insert(path, entry);
1108 self.mutation_revision = self.mutation_revision.wrapping_add(1);
1109 true
1110 }
1111
1112 pub fn insert_for_generation(
1114 &mut self,
1115 generation: u64,
1116 path: PathBuf,
1117 mtime: SystemTime,
1118 size: u64,
1119 content_hash: blake3::Hash,
1120 symbols: Vec<Symbol>,
1121 ) -> bool {
1122 if self.generation != generation {
1123 return false;
1124 }
1125 self.insert(path, mtime, size, content_hash, symbols)
1126 }
1127
1128 pub fn get(&self, path: &Path, mtime: SystemTime) -> Option<Vec<Symbol>> {
1130 self.entries.get(path).and_then(|cached| {
1131 cached_file_is_fresh(path, cached.mtime, cached.size, cached.content_hash, mtime)
1132 .then(|| cached.symbols.clone())
1133 })
1134 }
1135
1136 pub fn symbol_count_if_metadata_matches(
1142 &self,
1143 path: &Path,
1144 mtime: SystemTime,
1145 size: u64,
1146 ) -> Option<usize> {
1147 self.entries.get(path).and_then(|cached| {
1148 (cached.mtime == mtime && cached.size == size).then_some(cached.symbols.len())
1149 })
1150 }
1151
1152 pub fn contains_path_with_mtime(&self, path: &Path, mtime: SystemTime) -> bool {
1154 self.entries
1155 .get(path)
1156 .is_some_and(|cached| cached.mtime == mtime)
1157 }
1158
1159 pub fn load_from_disk(
1161 &mut self,
1162 storage_dir: &Path,
1163 project_key: &str,
1164 current_root: &Path,
1165 ) -> usize {
1166 self.load_from_disk_with_outcome(storage_dir, project_key, current_root)
1167 .loaded
1168 }
1169
1170 fn load_from_disk_with_outcome(
1171 &mut self,
1172 storage_dir: &Path,
1173 project_key: &str,
1174 current_root: &Path,
1175 ) -> SymbolCacheLoadOutcome {
1176 debug_assert!(current_root.is_absolute());
1177 let Some(cache) = symbol_cache_disk::read_from_disk(storage_dir, project_key) else {
1178 return SymbolCacheLoadOutcome::default();
1179 };
1180
1181 self.project_root = Some(current_root.to_path_buf());
1182 self.entries.clear();
1183 let mut outcome = SymbolCacheLoadOutcome::default();
1184
1185 for entry in cache.entries {
1186 let Some(path) =
1187 crate::search_index::cached_path_under_root(current_root, &entry.relative_path)
1188 else {
1189 outcome.needs_persistence = true;
1190 continue;
1191 };
1192 let cached_freshness = FileFreshness {
1193 mtime: entry.mtime,
1194 size: entry.size,
1195 content_hash: entry.content_hash,
1196 };
1197 let mtime = match cache_freshness::verify_file(&path, &cached_freshness) {
1198 FreshnessVerdict::HotFresh => entry.mtime,
1199 FreshnessVerdict::ContentFresh { new_mtime, .. } => {
1200 outcome.needs_persistence = true;
1201 new_mtime
1202 }
1203 FreshnessVerdict::Stale | FreshnessVerdict::Deleted => {
1204 outcome.needs_persistence = true;
1205 continue;
1206 }
1207 };
1208
1209 self.entries.insert(
1210 path,
1211 CachedSymbols {
1212 mtime,
1213 size: entry.size,
1214 content_hash: entry.content_hash,
1215 symbols: entry.symbols,
1216 },
1217 );
1218 outcome.loaded += 1;
1219 }
1220
1221 self.mutation_revision = if outcome.needs_persistence { 1 } else { 0 };
1222 self.persisted_revision = 0;
1223 outcome
1224 }
1225
1226 pub fn load_from_disk_for_generation(
1229 &mut self,
1230 generation: u64,
1231 storage_dir: &Path,
1232 project_key: &str,
1233 current_root: &Path,
1234 ) -> usize {
1235 self.load_from_disk_for_generation_with_outcome(
1236 generation,
1237 storage_dir,
1238 project_key,
1239 current_root,
1240 )
1241 .loaded
1242 }
1243
1244 pub(crate) fn load_from_disk_for_generation_with_outcome(
1245 &mut self,
1246 generation: u64,
1247 storage_dir: &Path,
1248 project_key: &str,
1249 current_root: &Path,
1250 ) -> SymbolCacheLoadOutcome {
1251 if self.generation != generation {
1252 return SymbolCacheLoadOutcome::default();
1253 }
1254 self.load_from_disk_with_outcome(storage_dir, project_key, current_root)
1255 }
1256
1257 pub fn invalidate(&mut self, path: &Path) {
1259 if self.entries.remove(path).is_some() {
1260 self.mutation_revision = self.mutation_revision.wrapping_add(1);
1261 }
1262 }
1263
1264 pub fn reset(&mut self) -> u64 {
1266 self.entries.clear();
1267 self.project_root = None;
1268 self.generation = self.generation.wrapping_add(1);
1269 self.mutation_revision = 0;
1270 self.persisted_revision = 0;
1271 self.generation
1272 }
1273
1274 pub fn generation(&self) -> u64 {
1276 self.generation
1277 }
1278
1279 pub(crate) fn needs_persistence(&self) -> bool {
1280 self.mutation_revision != self.persisted_revision
1281 }
1282
1283 pub(crate) fn persistence_revision(&self) -> u64 {
1284 self.mutation_revision
1285 }
1286
1287 pub(crate) fn mark_persisted_for_generation(
1288 &mut self,
1289 generation: u64,
1290 persisted_revision: u64,
1291 ) -> bool {
1292 if self.generation != generation || self.mutation_revision != persisted_revision {
1293 return false;
1294 }
1295 self.persisted_revision = persisted_revision;
1296 true
1297 }
1298
1299 pub fn contains_key(&self, path: &Path) -> bool {
1301 self.entries.contains_key(path)
1302 }
1303
1304 pub fn len(&self) -> usize {
1306 self.entries.len()
1307 }
1308
1309 pub fn estimated_memory(&self) -> crate::memory::MemoryEstimate {
1313 if self.entries.is_empty() {
1314 return crate::memory::MemoryEstimate::estimated(0)
1315 .count("entries", 0)
1316 .count("symbols", 0);
1317 }
1318 let entry_bytes = self.entries.iter().fold(0u64, |bytes, (path, cached)| {
1319 bytes
1320 .saturating_add(std::mem::size_of::<PathBuf>() as u64)
1321 .saturating_add(crate::memory::path_bytes(path))
1322 .saturating_add(std::mem::size_of::<CachedSymbols>() as u64)
1323 .saturating_add(symbols_estimated_bytes(&cached.symbols))
1324 });
1325 let project_root_bytes = self
1326 .project_root
1327 .as_deref()
1328 .map(crate::memory::path_bytes)
1329 .unwrap_or(0);
1330 let symbol_count = self
1331 .entries
1332 .values()
1333 .map(|cached| cached.symbols.len())
1334 .fold(0usize, usize::saturating_add);
1335 crate::memory::MemoryEstimate::estimated(entry_bytes.saturating_add(project_root_bytes))
1336 .count("entries", self.entries.len())
1337 .count("symbols", symbol_count)
1338 }
1339
1340 pub(crate) fn project_root(&self) -> Option<PathBuf> {
1341 self.project_root.clone()
1342 }
1343
1344 pub(crate) fn disk_entries(
1345 &self,
1346 ) -> Vec<(&PathBuf, SystemTime, u64, blake3::Hash, &Vec<Symbol>)> {
1347 self.entries
1357 .iter()
1358 .map(|(path, cached)| {
1359 (
1360 path,
1361 cached.mtime,
1362 cached.size,
1363 cached.content_hash,
1364 &cached.symbols,
1365 )
1366 })
1367 .collect()
1368 }
1369}
1370
1371pub struct FileParser {
1374 cache: HashMap<PathBuf, CachedTree>,
1375 parsers: HashMap<LangId, Parser>,
1376 symbol_cache: SharedSymbolCache,
1377 symbol_cache_generation: Option<u64>,
1378}
1379
1380impl FileParser {
1381 pub fn new() -> Self {
1383 Self::with_symbol_cache(Arc::new(RwLock::new(SymbolCache::new())))
1384 }
1385
1386 pub fn with_symbol_cache(symbol_cache: SharedSymbolCache) -> Self {
1388 Self::with_symbol_cache_generation(symbol_cache, None)
1389 }
1390
1391 pub fn with_symbol_cache_generation(
1393 symbol_cache: SharedSymbolCache,
1394 symbol_cache_generation: Option<u64>,
1395 ) -> Self {
1396 Self {
1397 cache: HashMap::new(),
1398 parsers: HashMap::new(),
1399 symbol_cache,
1400 symbol_cache_generation,
1401 }
1402 }
1403
1404 fn parser_for(&mut self, lang: LangId) -> Result<&mut Parser, AftError> {
1405 use std::collections::hash_map::Entry;
1406
1407 match self.parsers.entry(lang) {
1408 Entry::Occupied(entry) => Ok(entry.into_mut()),
1409 Entry::Vacant(entry) => {
1410 let grammar = grammar_for(lang);
1411 let mut parser = Parser::new();
1412 parser.set_language(&grammar).map_err(|e| {
1413 crate::slog_error!("grammar init failed for {:?}: {}", lang, e);
1414 AftError::ParseError {
1415 message: format!("grammar init failed for {:?}: {}", lang, e),
1416 }
1417 })?;
1418 Ok(entry.insert(parser))
1419 }
1420 }
1421 }
1422
1423 pub fn symbol_cache_len(&self) -> usize {
1425 self.symbol_cache
1426 .read()
1427 .map(|cache| cache.len())
1428 .unwrap_or(0)
1429 }
1430
1431 pub fn symbol_cache(&self) -> SharedSymbolCache {
1433 Arc::clone(&self.symbol_cache)
1434 }
1435
1436 pub fn parse(&mut self, path: &Path) -> Result<(&Tree, LangId), AftError> {
1439 let lang = detect_language(path).ok_or_else(|| AftError::InvalidRequest {
1440 message: format!(
1441 "unsupported file extension: {}",
1442 path.extension()
1443 .and_then(|e| e.to_str())
1444 .unwrap_or("<none>")
1445 ),
1446 })?;
1447
1448 let canon = path.to_path_buf();
1449 let current_mtime = std::fs::metadata(path)
1450 .and_then(|m| m.modified())
1451 .map_err(|e| AftError::FileNotFound {
1452 path: format!("{}: {}", path.display(), e),
1453 })?;
1454
1455 let needs_reparse = match self.cache.get(&canon) {
1459 Some(cached) => !cached_file_is_fresh(
1460 path,
1461 cached.mtime,
1462 cached.size,
1463 cached.content_hash,
1464 current_mtime,
1465 ),
1466 None => true,
1467 };
1468
1469 if needs_reparse {
1470 let source = std::fs::read_to_string(path).map_err(|e| AftError::FileNotFound {
1471 path: format!("{}: {}", path.display(), e),
1472 })?;
1473
1474 let tree = self.parser_for(lang)?.parse(&source, None).ok_or_else(|| {
1475 crate::slog_error!("parse failed for {}", path.display());
1476 AftError::ParseError {
1477 message: format!("tree-sitter parse returned None for {}", path.display()),
1478 }
1479 })?;
1480
1481 self.cache.insert(
1482 canon.clone(),
1483 CachedTree {
1484 mtime: current_mtime,
1485 size: source.len() as u64,
1486 content_hash: content_hash_for_source(&source),
1487 tree,
1488 },
1489 );
1490 }
1491
1492 let cached = self.cache.get(&canon).ok_or_else(|| AftError::ParseError {
1493 message: format!("parser cache missing entry for {}", path.display()),
1494 })?;
1495 Ok((&cached.tree, lang))
1496 }
1497
1498 pub fn parse_cloned(&mut self, path: &Path) -> Result<(Tree, LangId), AftError> {
1503 let (tree, lang) = self.parse(path)?;
1504 Ok((tree.clone(), lang))
1505 }
1506
1507 fn parse_with_source(
1519 &mut self,
1520 path: &Path,
1521 source: &str,
1522 current_mtime: std::time::SystemTime,
1523 size: u64,
1524 content_hash: blake3::Hash,
1525 ) -> Result<(&Tree, LangId), AftError> {
1526 let lang = detect_language(path).ok_or_else(|| AftError::InvalidRequest {
1527 message: format!(
1528 "unsupported file extension: {}",
1529 path.extension()
1530 .and_then(|e| e.to_str())
1531 .unwrap_or("<none>")
1532 ),
1533 })?;
1534
1535 let canon = path.to_path_buf();
1536 let needs_reparse = match self.cache.get(&canon) {
1537 Some(cached) => !cached_file_is_fresh(
1538 path,
1539 cached.mtime,
1540 cached.size,
1541 cached.content_hash,
1542 current_mtime,
1543 ),
1544 None => true,
1545 };
1546
1547 if needs_reparse {
1548 let tree = self.parser_for(lang)?.parse(source, None).ok_or_else(|| {
1549 crate::slog_error!("parse failed for {}", path.display());
1550 AftError::ParseError {
1551 message: format!("tree-sitter parse returned None for {}", path.display()),
1552 }
1553 })?;
1554
1555 self.cache.insert(
1556 canon.clone(),
1557 CachedTree {
1558 mtime: current_mtime,
1559 size,
1560 content_hash,
1561 tree,
1562 },
1563 );
1564 }
1565
1566 let cached = self.cache.get(&canon).ok_or_else(|| AftError::ParseError {
1567 message: format!("parser cache missing entry for {}", path.display()),
1568 })?;
1569 Ok((&cached.tree, lang))
1570 }
1571
1572 pub fn extract_symbols(&mut self, path: &Path) -> Result<Vec<Symbol>, AftError> {
1576 self.extract_symbols_with_cache_status(path)
1577 .map(|(symbols, _)| symbols)
1578 }
1579
1580 pub(crate) fn extract_symbols_with_cache_status(
1581 &mut self,
1582 path: &Path,
1583 ) -> Result<(Vec<Symbol>, bool), AftError> {
1584 let canon = path.to_path_buf();
1585 let current_mtime = std::fs::metadata(path)
1586 .and_then(|m| m.modified())
1587 .map_err(|e| AftError::FileNotFound {
1588 path: format!("{}: {}", path.display(), e),
1589 })?;
1590
1591 if let Some(symbols) = self
1593 .symbol_cache
1594 .read()
1595 .map_err(|_| AftError::ParseError {
1596 message: "symbol cache lock poisoned".to_string(),
1597 })?
1598 .get(&canon, current_mtime)
1599 {
1600 return Ok((symbols, false));
1601 }
1602
1603 let source = std::fs::read_to_string(path).map_err(|e| AftError::FileNotFound {
1604 path: format!("{}: {}", path.display(), e),
1605 })?;
1606 let size = source.len() as u64;
1607 let content_hash = content_hash_for_source(&source);
1608
1609 let symbols = {
1610 let (tree, lang) =
1613 self.parse_with_source(path, &source, current_mtime, size, content_hash)?;
1614 extract_symbols_from_tree(&source, tree, lang)?
1615 };
1616
1617 let mut symbol_cache = self
1618 .symbol_cache
1619 .write()
1620 .map_err(|_| AftError::ParseError {
1621 message: "symbol cache lock poisoned".to_string(),
1622 })?;
1623 let cache_changed = if let Some(generation) = self.symbol_cache_generation {
1624 symbol_cache.insert_for_generation(
1625 generation,
1626 canon,
1627 current_mtime,
1628 size,
1629 content_hash,
1630 symbols.clone(),
1631 )
1632 } else {
1633 symbol_cache.insert(canon, current_mtime, size, content_hash, symbols.clone())
1634 };
1635
1636 Ok((symbols, cache_changed))
1637 }
1638
1639 pub fn invalidate_symbols(&mut self, path: &Path) {
1641 if let Ok(mut symbol_cache) = self.symbol_cache.write() {
1642 symbol_cache.invalidate(path);
1643 }
1644 self.cache.remove(path);
1645 }
1646}
1647
1648pub fn extract_symbols_from_tree(
1653 source: &str,
1654 tree: &Tree,
1655 lang: LangId,
1656) -> Result<Vec<Symbol>, AftError> {
1657 let root = tree.root_node();
1658
1659 if lang == LangId::Rust {
1660 return extract_rs_symbols(source, &root);
1661 }
1662 if lang == LangId::Html {
1663 return extract_html_symbols(source, &root);
1664 }
1665 if lang == LangId::Markdown {
1666 return extract_md_symbols(source, &root);
1667 }
1668 if lang == LangId::Vue {
1669 return extract_vue_symbols(source, &root);
1670 }
1671 if lang == LangId::Json {
1672 return extract_json_symbols(source, &root);
1673 }
1674 if lang == LangId::Yaml {
1675 return extract_yaml_symbols(source, &root);
1676 }
1677 if lang == LangId::Toml {
1678 return extract_toml_symbols(source, &root);
1679 }
1680
1681 let query = cached_query_for(lang)?.ok_or_else(|| AftError::InvalidRequest {
1682 message: format!("no query patterns implemented for {:?} yet", lang),
1683 })?;
1684
1685 match lang {
1686 LangId::TypeScript | LangId::Tsx => extract_ts_symbols(source, &root, query),
1687 LangId::JavaScript => extract_js_symbols(source, &root, query),
1688 LangId::Python => extract_py_symbols(source, &root, query),
1689 LangId::Go => extract_go_symbols(source, &root, query),
1690 LangId::C => extract_c_symbols(source, &root, query),
1691 LangId::Cpp | LangId::Cuda | LangId::Metal => {
1692 extract_cpp_symbols(source, &root, query, lang)
1693 }
1694 LangId::Zig => extract_zig_symbols(source, &root, query),
1695 LangId::CSharp => extract_csharp_symbols(source, &root, query),
1696 LangId::Bash => extract_bash_symbols(source, &root, query),
1697 LangId::Solidity => extract_solidity_symbols(source, &root, query),
1698 LangId::Scss => extract_scss_symbols(source, &root, query),
1699 LangId::Scala => extract_scala_symbols(source, &root, query),
1700 LangId::Java => extract_java_symbols(source, &root, query),
1701 LangId::Ruby => extract_ruby_symbols(source, &root, query),
1702 LangId::Kotlin => extract_kotlin_symbols(source, &root, query),
1703 LangId::Swift => extract_swift_symbols(source, &root, query),
1704 LangId::Php => extract_php_symbols(source, &root, query),
1705 LangId::Lua => extract_lua_symbols(source, &root, query),
1706 LangId::Perl => extract_perl_symbols(source, &root, query),
1707 LangId::Pascal => extract_pascal_symbols(source, &root, query),
1708 LangId::R => extract_r_symbols(source, &root, query),
1709 LangId::Groovy => extract_groovy_symbols(source, &root, query),
1710 LangId::ObjC => extract_objc_symbols(source, &root, query),
1711 LangId::Rust
1712 | LangId::Html
1713 | LangId::Markdown
1714 | LangId::Vue
1715 | LangId::Json
1716 | LangId::Yaml
1717 | LangId::Toml => unreachable!("handled before query lookup"),
1718 }
1719}
1720
1721pub(crate) fn node_range(node: &Node) -> Range {
1723 let start = node.start_position();
1724 let end = node.end_position();
1725 Range {
1726 start_line: start.row as u32,
1727 start_col: start.column as u32,
1728 end_line: end.row as u32,
1729 end_col: end.column as u32,
1730 }
1731}
1732
1733pub(crate) fn node_range_with_decorators(node: &Node, source: &str, lang: LangId) -> Range {
1739 if matches!(lang, LangId::Python) {
1740 if let Some(parent) = node.parent() {
1741 if parent.kind() == "decorated_definition" {
1742 return node_range(&parent);
1743 }
1744 }
1745 }
1746
1747 if matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
1755 if let Some(parent) = node.parent() {
1756 if parent.kind() == "export_statement" {
1757 return node_range_with_decorators_inner(&parent, source, lang);
1758 }
1759 }
1760 }
1761
1762 node_range_with_decorators_inner(node, source, lang)
1763}
1764
1765fn node_range_with_decorators_inner(node: &Node, source: &str, lang: LangId) -> Range {
1769 let mut range = node_range(node);
1770
1771 let mut current = *node;
1772 while let Some(prev) = current.prev_sibling() {
1773 let kind = prev.kind();
1774 let should_include = match lang {
1775 LangId::Rust => {
1776 kind == "attribute_item"
1778 || (kind == "line_comment"
1780 && node_text(source, &prev).starts_with("///"))
1781 || (kind == "block_comment"
1783 && node_text(source, &prev).starts_with("/**"))
1784 }
1785 LangId::TypeScript | LangId::Tsx | LangId::JavaScript => {
1786 kind == "decorator"
1788 || (kind == "comment"
1791 && node_text(source, &prev).starts_with("/**")
1792 && is_adjacent_line(&prev, ¤t, source))
1793 }
1794 LangId::Go
1795 | LangId::C
1796 | LangId::Cpp
1797 | LangId::Cuda
1798 | LangId::Metal
1799 | LangId::ObjC
1800 | LangId::Zig
1801 | LangId::CSharp
1802 | LangId::Bash
1803 | LangId::Pascal => {
1804 kind == "comment" && is_adjacent_line(&prev, ¤t, source)
1806 }
1807 LangId::Solidity
1808 | LangId::Scss
1809 | LangId::Scala
1810 | LangId::Java
1811 | LangId::Kotlin
1812 | LangId::Swift
1813 | LangId::Php
1814 | LangId::Groovy => {
1815 let text = node_text(source, &prev);
1817 (kind == "comment" || kind == "line_comment" || kind == "block_comment")
1818 && (text.starts_with("///") || text.starts_with("/**"))
1819 && is_adjacent_line(&prev, ¤t, source)
1820 }
1821 LangId::Ruby | LangId::Lua | LangId::R => {
1822 let text = node_text(source, &prev);
1824 kind == "comment"
1825 && (text.starts_with('#') || text.starts_with("---"))
1826 && is_adjacent_line(&prev, ¤t, source)
1827 }
1828 LangId::Perl => false,
1829 LangId::Python => {
1830 false
1832 }
1833 LangId::Html
1834 | LangId::Markdown
1835 | LangId::Vue
1836 | LangId::Json
1837 | LangId::Yaml
1838 | LangId::Toml => false,
1839 };
1840
1841 if should_include {
1842 range.start_line = prev.start_position().row as u32;
1843 range.start_col = prev.start_position().column as u32;
1844 current = prev;
1845 } else {
1846 break;
1847 }
1848 }
1849
1850 range
1851}
1852
1853fn is_adjacent_line(upper: &Node, lower: &Node, source: &str) -> bool {
1855 let upper_end = upper.end_position().row;
1856 let lower_start = lower.start_position().row;
1857
1858 if lower_start == 0 || lower_start <= upper_end {
1859 return true;
1860 }
1861
1862 let lines: Vec<&str> = source.lines().collect();
1864 for row in (upper_end + 1)..lower_start {
1865 if row < lines.len() && lines[row].trim().is_empty() {
1866 return false;
1867 }
1868 }
1869 true
1870}
1871
1872pub(crate) fn node_text<'a>(source: &'a str, node: &Node) -> &'a str {
1874 &source[node.byte_range()]
1875}
1876
1877fn lexical_declaration_has_function_value(node: &Node) -> bool {
1878 let mut cursor = node.walk();
1879 if !cursor.goto_first_child() {
1880 return false;
1881 }
1882
1883 loop {
1884 let child = cursor.node();
1885 if matches!(
1886 child.kind(),
1887 "arrow_function" | "function_expression" | "generator_function"
1888 ) {
1889 return true;
1890 }
1891
1892 if lexical_declaration_has_function_value(&child) {
1893 return true;
1894 }
1895
1896 if !cursor.goto_next_sibling() {
1897 break;
1898 }
1899 }
1900
1901 false
1902}
1903
1904fn variable_declarator_has_function_value(node: &Node) -> bool {
1905 node.child_by_field_name("value").is_some_and(|value| {
1906 matches!(
1907 value.kind(),
1908 "arrow_function" | "function_expression" | "generator_function"
1909 )
1910 })
1911}
1912
1913fn collect_export_ranges(source: &str, root: &Node, query: &Query) -> Vec<std::ops::Range<usize>> {
1915 let export_idx = query
1916 .capture_names()
1917 .iter()
1918 .position(|n| *n == "export.stmt");
1919 let export_idx = match export_idx {
1920 Some(i) => i as u32,
1921 None => return vec![],
1922 };
1923
1924 let mut cursor = QueryCursor::new();
1925 let mut ranges = Vec::new();
1926 let mut matches = cursor.matches(query, *root, source.as_bytes());
1927
1928 while let Some(m) = {
1929 matches.advance();
1930 matches.get()
1931 } {
1932 for cap in m.captures {
1933 if cap.index == export_idx {
1934 ranges.push(cap.node.byte_range());
1935 }
1936 }
1937 }
1938 ranges
1939}
1940
1941fn is_exported(node: &Node, export_ranges: &[std::ops::Range<usize>]) -> bool {
1943 let r = node.byte_range();
1944 export_ranges
1945 .iter()
1946 .any(|er| er.start <= r.start && r.end <= er.end)
1947}
1948
1949fn collect_exported_symbol_names(source: &str, root: &Node) -> HashSet<String> {
1957 let mut exported = HashSet::new();
1958 let mut cursor = root.walk();
1959 loop {
1960 let node = cursor.node();
1961 if node.kind() == "export_statement" {
1962 collect_names_from_export_statement(source, &node, &mut exported);
1963 }
1964 if cursor.goto_first_child() {
1965 continue;
1966 }
1967 loop {
1968 if cursor.goto_next_sibling() {
1969 break;
1970 }
1971 if !cursor.goto_parent() {
1972 return exported;
1973 }
1974 }
1975 }
1976}
1977
1978fn collect_names_from_export_statement(source: &str, node: &Node, exported: &mut HashSet<String>) {
1979 let mut cursor = node.walk();
1980 if !cursor.goto_first_child() {
1981 return;
1982 }
1983
1984 let mut saw_default = false;
1985 loop {
1986 let child = cursor.node();
1987 match child.kind() {
1988 "default" => saw_default = true,
1989 "export_clause" => collect_names_from_export_clause(source, &child, exported),
1990 "identifier" | "type_identifier" | "property_identifier" if saw_default => {
1991 exported.insert(node_text(source, &child).to_string());
1992 return;
1993 }
1994 _ => {}
1995 }
1996 if !cursor.goto_next_sibling() {
1997 break;
1998 }
1999 }
2000}
2001
2002fn collect_names_from_export_clause(source: &str, node: &Node, exported: &mut HashSet<String>) {
2003 let mut cursor = node.walk();
2004 if !cursor.goto_first_child() {
2005 return;
2006 }
2007
2008 loop {
2009 let child = cursor.node();
2010 if child.kind() == "export_specifier" {
2011 if let Some(exported_name) = last_identifier_text(source, &child) {
2012 exported.insert(exported_name);
2013 }
2014 }
2015 if !cursor.goto_next_sibling() {
2016 break;
2017 }
2018 }
2019}
2020
2021fn last_identifier_text(source: &str, node: &Node) -> Option<String> {
2022 let mut cursor = node.walk();
2023 if !cursor.goto_first_child() {
2024 return None;
2025 }
2026
2027 let mut last = None;
2028 loop {
2029 let child = cursor.node();
2030 if matches!(
2031 child.kind(),
2032 "identifier"
2033 | "type_identifier"
2034 | "property_identifier"
2035 | "shorthand_property_identifier"
2036 ) {
2037 last = Some(node_text(source, &child).to_string());
2038 }
2039 if !cursor.goto_next_sibling() {
2040 break;
2041 }
2042 }
2043 last
2044}
2045
2046fn mark_named_exports(symbols: &mut [Symbol], exported_names: &HashSet<String>) {
2047 for symbol in symbols {
2048 if symbol.scope_chain.is_empty()
2049 && symbol.parent.is_none()
2050 && exported_names.contains(&symbol.name)
2051 {
2052 symbol.exported = true;
2053 }
2054 }
2055}
2056
2057fn extract_signature(source: &str, node: &Node) -> String {
2059 let text = node_text(source, node);
2060 let first_line = text.lines().next().unwrap_or(text);
2061 let trimmed = first_line.trim_end();
2063 let trimmed = trimmed.strip_suffix('{').unwrap_or(trimmed).trim_end();
2064 trimmed.to_string()
2065}
2066
2067fn push_default_export_symbol(
2068 symbols: &mut Vec<Symbol>,
2069 source: &str,
2070 lang: LangId,
2071 body_node: Node,
2072 def_node: Node,
2073) {
2074 let kind = if body_node.kind() == "class" {
2075 SymbolKind::Class
2076 } else {
2077 SymbolKind::Function
2078 };
2079
2080 symbols.push(Symbol {
2081 name: "default".to_string(),
2082 kind,
2083 range: node_range_with_decorators(&def_node, source, lang),
2084 signature: Some(extract_signature(source, &def_node)),
2085 scope_chain: vec![],
2086 exported: true,
2087 parent: None,
2088 });
2089}
2090
2091fn extract_ts_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2093 let lang = LangId::TypeScript;
2094 let capture_names = query.capture_names();
2095
2096 let export_ranges = collect_export_ranges(source, root, query);
2097 let exported_names = collect_exported_symbol_names(source, root);
2098
2099 let mut symbols = Vec::new();
2100 let mut cursor = QueryCursor::new();
2101 let mut matches = cursor.matches(query, *root, source.as_bytes());
2102
2103 while let Some(m) = {
2104 matches.advance();
2105 matches.get()
2106 } {
2107 let mut fn_name_node = None;
2109 let mut fn_def_node = None;
2110 let mut arrow_name_node = None;
2111 let mut arrow_def_node = None;
2112 let mut arrow_decl_node = None;
2113 let mut class_name_node = None;
2114 let mut class_def_node = None;
2115 let mut method_class_name_node = None;
2116 let mut method_name_node = None;
2117 let mut method_def_node = None;
2118 let mut interface_name_node = None;
2119 let mut interface_def_node = None;
2120 let mut enum_name_node = None;
2121 let mut enum_def_node = None;
2122 let mut type_alias_name_node = None;
2123 let mut type_alias_def_node = None;
2124 let mut var_name_node = None;
2125 let mut var_def_node = None;
2126 let mut var_decl_node = None;
2127 let mut default_body_node = None;
2128 let mut default_def_node = None;
2129
2130 for cap in m.captures {
2131 let Some(&name) = capture_names.get(cap.index as usize) else {
2132 continue;
2133 };
2134 match name {
2135 "fn.name" => fn_name_node = Some(cap.node),
2136 "fn.def" => fn_def_node = Some(cap.node),
2137 "arrow.name" => arrow_name_node = Some(cap.node),
2138 "arrow.def" => arrow_def_node = Some(cap.node),
2139 "arrow.decl" => arrow_decl_node = Some(cap.node),
2140 "class.name" => class_name_node = Some(cap.node),
2141 "class.def" => class_def_node = Some(cap.node),
2142 "method.class_name" => method_class_name_node = Some(cap.node),
2143 "method.name" => method_name_node = Some(cap.node),
2144 "method.def" => method_def_node = Some(cap.node),
2145 "interface.name" => interface_name_node = Some(cap.node),
2146 "interface.def" => interface_def_node = Some(cap.node),
2147 "enum.name" => enum_name_node = Some(cap.node),
2148 "enum.def" => enum_def_node = Some(cap.node),
2149 "type_alias.name" => type_alias_name_node = Some(cap.node),
2150 "type_alias.def" => type_alias_def_node = Some(cap.node),
2151 "var.name" => var_name_node = Some(cap.node),
2152 "var.def" => var_def_node = Some(cap.node),
2153 "var.decl" => var_decl_node = Some(cap.node),
2154 "default.body" => default_body_node = Some(cap.node),
2155 "default.def" => default_def_node = Some(cap.node),
2156 _ => {}
2157 }
2158 }
2159
2160 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2162 symbols.push(Symbol {
2163 name: node_text(source, &name_node).to_string(),
2164 kind: SymbolKind::Function,
2165 range: node_range_with_decorators(&def_node, source, lang),
2166 signature: Some(extract_signature(source, &def_node)),
2167 scope_chain: vec![],
2168 exported: is_exported(&def_node, &export_ranges),
2169 parent: None,
2170 });
2171 }
2172
2173 if let (Some(name_node), Some(def_node)) = (arrow_name_node, arrow_def_node) {
2175 let range_node = arrow_decl_node.unwrap_or(def_node);
2176 symbols.push(Symbol {
2177 name: node_text(source, &name_node).to_string(),
2178 kind: SymbolKind::Function,
2179 range: node_range_with_decorators(&range_node, source, lang),
2180 signature: Some(extract_signature(source, &def_node)),
2181 scope_chain: vec![],
2182 exported: is_exported(&def_node, &export_ranges),
2183 parent: None,
2184 });
2185 }
2186
2187 if let (Some(body_node), Some(def_node)) = (default_body_node, default_def_node) {
2189 push_default_export_symbol(&mut symbols, source, lang, body_node, def_node);
2190 }
2191
2192 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
2194 symbols.push(Symbol {
2195 name: node_text(source, &name_node).to_string(),
2196 kind: SymbolKind::Class,
2197 range: node_range_with_decorators(&def_node, source, lang),
2198 signature: Some(extract_signature(source, &def_node)),
2199 scope_chain: vec![],
2200 exported: is_exported(&def_node, &export_ranges),
2201 parent: None,
2202 });
2203 }
2204
2205 if let (Some(class_name_node), Some(name_node), Some(def_node)) =
2207 (method_class_name_node, method_name_node, method_def_node)
2208 {
2209 let class_name = node_text(source, &class_name_node).to_string();
2210 symbols.push(Symbol {
2211 name: node_text(source, &name_node).to_string(),
2212 kind: SymbolKind::Method,
2213 range: node_range_with_decorators(&def_node, source, lang),
2214 signature: Some(extract_signature(source, &def_node)),
2215 scope_chain: vec![class_name.clone()],
2216 exported: false, parent: Some(class_name),
2218 });
2219 }
2220
2221 if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
2223 symbols.push(Symbol {
2224 name: node_text(source, &name_node).to_string(),
2225 kind: SymbolKind::Interface,
2226 range: node_range_with_decorators(&def_node, source, lang),
2227 signature: Some(extract_signature(source, &def_node)),
2228 scope_chain: vec![],
2229 exported: is_exported(&def_node, &export_ranges),
2230 parent: None,
2231 });
2232 }
2233
2234 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
2236 symbols.push(Symbol {
2237 name: node_text(source, &name_node).to_string(),
2238 kind: SymbolKind::Enum,
2239 range: node_range_with_decorators(&def_node, source, lang),
2240 signature: Some(extract_signature(source, &def_node)),
2241 scope_chain: vec![],
2242 exported: is_exported(&def_node, &export_ranges),
2243 parent: None,
2244 });
2245 }
2246
2247 if let (Some(name_node), Some(def_node)) = (type_alias_name_node, type_alias_def_node) {
2249 symbols.push(Symbol {
2250 name: node_text(source, &name_node).to_string(),
2251 kind: SymbolKind::TypeAlias,
2252 range: node_range_with_decorators(&def_node, source, lang),
2253 signature: Some(extract_signature(source, &def_node)),
2254 scope_chain: vec![],
2255 exported: is_exported(&def_node, &export_ranges),
2256 parent: None,
2257 });
2258 }
2259
2260 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
2262 let is_top_level = def_node
2264 .parent()
2265 .map(|p| p.kind() == "program" || p.kind() == "export_statement")
2266 .unwrap_or(false);
2267 let range_node = var_decl_node.unwrap_or(def_node);
2268 let is_function_like = if range_node.kind() == "variable_declarator" {
2269 variable_declarator_has_function_value(&range_node)
2270 } else {
2271 lexical_declaration_has_function_value(&def_node)
2272 };
2273 let name = node_text(source, &name_node).to_string();
2274 let already_captured = symbols.iter().any(|s| s.name == name);
2275 if is_top_level && !is_function_like && !already_captured {
2276 symbols.push(Symbol {
2277 name,
2278 kind: SymbolKind::Variable,
2279 range: node_range_with_decorators(&range_node, source, lang),
2280 signature: Some(extract_signature(source, &def_node)),
2281 scope_chain: vec![],
2282 exported: is_exported(&def_node, &export_ranges),
2283 parent: None,
2284 });
2285 }
2286 }
2287 }
2288
2289 mark_named_exports(&mut symbols, &exported_names);
2290
2291 dedup_symbols(&mut symbols);
2293 Ok(symbols)
2294}
2295
2296fn extract_js_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2298 let lang = LangId::JavaScript;
2299 let capture_names = query.capture_names();
2300
2301 let export_ranges = collect_export_ranges(source, root, query);
2302 let exported_names = collect_exported_symbol_names(source, root);
2303
2304 let mut symbols = Vec::new();
2305 let mut cursor = QueryCursor::new();
2306 let mut matches = cursor.matches(query, *root, source.as_bytes());
2307
2308 while let Some(m) = {
2309 matches.advance();
2310 matches.get()
2311 } {
2312 let mut fn_name_node = None;
2313 let mut fn_def_node = None;
2314 let mut arrow_name_node = None;
2315 let mut arrow_def_node = None;
2316 let mut arrow_decl_node = None;
2317 let mut class_name_node = None;
2318 let mut class_def_node = None;
2319 let mut method_class_name_node = None;
2320 let mut method_name_node = None;
2321 let mut method_def_node = None;
2322 let mut default_body_node = None;
2323 let mut default_def_node = None;
2324 let mut var_name_node = None;
2325 let mut var_def_node = None;
2326 let mut var_decl_node = None;
2327
2328 for cap in m.captures {
2329 let Some(&name) = capture_names.get(cap.index as usize) else {
2330 continue;
2331 };
2332 match name {
2333 "fn.name" => fn_name_node = Some(cap.node),
2334 "fn.def" => fn_def_node = Some(cap.node),
2335 "arrow.name" => arrow_name_node = Some(cap.node),
2336 "arrow.def" => arrow_def_node = Some(cap.node),
2337 "arrow.decl" => arrow_decl_node = Some(cap.node),
2338 "class.name" => class_name_node = Some(cap.node),
2339 "class.def" => class_def_node = Some(cap.node),
2340 "method.class_name" => method_class_name_node = Some(cap.node),
2341 "method.name" => method_name_node = Some(cap.node),
2342 "method.def" => method_def_node = Some(cap.node),
2343 "default.body" => default_body_node = Some(cap.node),
2344 "default.def" => default_def_node = Some(cap.node),
2345 "var.name" => var_name_node = Some(cap.node),
2346 "var.def" => var_def_node = Some(cap.node),
2347 "var.decl" => var_decl_node = Some(cap.node),
2348 _ => {}
2349 }
2350 }
2351
2352 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2353 symbols.push(Symbol {
2354 name: node_text(source, &name_node).to_string(),
2355 kind: SymbolKind::Function,
2356 range: node_range_with_decorators(&def_node, source, lang),
2357 signature: Some(extract_signature(source, &def_node)),
2358 scope_chain: vec![],
2359 exported: is_exported(&def_node, &export_ranges),
2360 parent: None,
2361 });
2362 }
2363
2364 if let (Some(name_node), Some(def_node)) = (arrow_name_node, arrow_def_node) {
2365 let range_node = arrow_decl_node.unwrap_or(def_node);
2366 symbols.push(Symbol {
2367 name: node_text(source, &name_node).to_string(),
2368 kind: SymbolKind::Function,
2369 range: node_range_with_decorators(&range_node, source, lang),
2370 signature: Some(extract_signature(source, &def_node)),
2371 scope_chain: vec![],
2372 exported: is_exported(&def_node, &export_ranges),
2373 parent: None,
2374 });
2375 }
2376
2377 if let (Some(body_node), Some(def_node)) = (default_body_node, default_def_node) {
2378 push_default_export_symbol(&mut symbols, source, lang, body_node, def_node);
2379 }
2380
2381 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
2382 symbols.push(Symbol {
2383 name: node_text(source, &name_node).to_string(),
2384 kind: SymbolKind::Class,
2385 range: node_range_with_decorators(&def_node, source, lang),
2386 signature: Some(extract_signature(source, &def_node)),
2387 scope_chain: vec![],
2388 exported: is_exported(&def_node, &export_ranges),
2389 parent: None,
2390 });
2391 }
2392
2393 if let (Some(class_name_node), Some(name_node), Some(def_node)) =
2394 (method_class_name_node, method_name_node, method_def_node)
2395 {
2396 let class_name = node_text(source, &class_name_node).to_string();
2397 symbols.push(Symbol {
2398 name: node_text(source, &name_node).to_string(),
2399 kind: SymbolKind::Method,
2400 range: node_range_with_decorators(&def_node, source, lang),
2401 signature: Some(extract_signature(source, &def_node)),
2402 scope_chain: vec![class_name.clone()],
2403 exported: false,
2404 parent: Some(class_name),
2405 });
2406 }
2407
2408 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
2414 let is_top_level = def_node
2415 .parent()
2416 .map(|p| p.kind() == "program" || p.kind() == "export_statement")
2417 .unwrap_or(false);
2418 let range_node = var_decl_node.unwrap_or(def_node);
2419 let is_function_like = if range_node.kind() == "variable_declarator" {
2420 variable_declarator_has_function_value(&range_node)
2421 } else {
2422 lexical_declaration_has_function_value(&def_node)
2423 };
2424 let name = node_text(source, &name_node).to_string();
2425 let already_captured = symbols.iter().any(|s| s.name == name);
2426 if is_top_level && !is_function_like && !already_captured {
2427 symbols.push(Symbol {
2428 name,
2429 kind: SymbolKind::Variable,
2430 range: node_range_with_decorators(&range_node, source, lang),
2431 signature: Some(extract_signature(source, &def_node)),
2432 scope_chain: vec![],
2433 exported: is_exported(&def_node, &export_ranges),
2434 parent: None,
2435 });
2436 }
2437 }
2438 }
2439
2440 mark_named_exports(&mut symbols, &exported_names);
2441 dedup_symbols(&mut symbols);
2442 Ok(symbols)
2443}
2444
2445fn py_scope_chain(node: &Node, source: &str) -> Vec<String> {
2448 let mut chain = Vec::new();
2449 let mut current = node.parent();
2450 while let Some(parent) = current {
2451 if parent.kind() == "class_definition" {
2452 if let Some(name_node) = parent.child_by_field_name("name") {
2453 chain.push(node_text(source, &name_node).to_string());
2454 }
2455 }
2456 current = parent.parent();
2457 }
2458 chain.reverse();
2459 chain
2460}
2461
2462fn extract_py_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2464 let lang = LangId::Python;
2465 let capture_names = query.capture_names();
2466
2467 let mut symbols = Vec::new();
2468 let mut cursor = QueryCursor::new();
2469 let mut matches = cursor.matches(query, *root, source.as_bytes());
2470
2471 let mut decorated_fn_lines = std::collections::HashSet::new();
2473
2474 {
2476 let mut cursor2 = QueryCursor::new();
2477 let mut matches2 = cursor2.matches(query, *root, source.as_bytes());
2478 while let Some(m) = {
2479 matches2.advance();
2480 matches2.get()
2481 } {
2482 let mut dec_def_node = None;
2483 let mut dec_decorator_node = None;
2484
2485 for cap in m.captures {
2486 let Some(&name) = capture_names.get(cap.index as usize) else {
2487 continue;
2488 };
2489 match name {
2490 "dec.def" => dec_def_node = Some(cap.node),
2491 "dec.decorator" => dec_decorator_node = Some(cap.node),
2492 _ => {}
2493 }
2494 }
2495
2496 if let (Some(def_node), Some(_dec_node)) = (dec_def_node, dec_decorator_node) {
2497 let mut child_cursor = def_node.walk();
2499 if child_cursor.goto_first_child() {
2500 loop {
2501 let child = child_cursor.node();
2502 if child.kind() == "function_definition"
2503 || child.kind() == "class_definition"
2504 {
2505 decorated_fn_lines.insert(child.start_position().row);
2506 }
2507 if !child_cursor.goto_next_sibling() {
2508 break;
2509 }
2510 }
2511 }
2512 }
2513 }
2514 }
2515
2516 while let Some(m) = {
2517 matches.advance();
2518 matches.get()
2519 } {
2520 let mut fn_name_node = None;
2521 let mut fn_def_node = None;
2522 let mut class_name_node = None;
2523 let mut class_def_node = None;
2524
2525 for cap in m.captures {
2526 let Some(&name) = capture_names.get(cap.index as usize) else {
2527 continue;
2528 };
2529 match name {
2530 "fn.name" => fn_name_node = Some(cap.node),
2531 "fn.def" => fn_def_node = Some(cap.node),
2532 "class.name" => class_name_node = Some(cap.node),
2533 "class.def" => class_def_node = Some(cap.node),
2534 _ => {}
2535 }
2536 }
2537
2538 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2540 let scope = py_scope_chain(&def_node, source);
2541 let is_method = !scope.is_empty();
2542 let name = node_text(source, &name_node).to_string();
2543 let kind = if is_method {
2545 SymbolKind::Method
2546 } else {
2547 SymbolKind::Function
2548 };
2549
2550 let sig = if decorated_fn_lines.contains(&def_node.start_position().row) {
2552 let mut sig_parts = Vec::new();
2554 let mut parent = def_node.parent();
2555 while let Some(p) = parent {
2556 if p.kind() == "decorated_definition" {
2557 let mut dc = p.walk();
2559 if dc.goto_first_child() {
2560 loop {
2561 if dc.node().kind() == "decorator" {
2562 sig_parts.push(node_text(source, &dc.node()).to_string());
2563 }
2564 if !dc.goto_next_sibling() {
2565 break;
2566 }
2567 }
2568 }
2569 break;
2570 }
2571 parent = p.parent();
2572 }
2573 sig_parts.push(extract_signature(source, &def_node));
2574 Some(sig_parts.join("\n"))
2575 } else {
2576 Some(extract_signature(source, &def_node))
2577 };
2578
2579 symbols.push(Symbol {
2580 name,
2581 kind,
2582 range: node_range_with_decorators(&def_node, source, lang),
2583 signature: sig,
2584 scope_chain: scope.clone(),
2585 exported: false, parent: scope.last().cloned(),
2587 });
2588 }
2589
2590 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
2592 let scope = py_scope_chain(&def_node, source);
2593
2594 let sig = if decorated_fn_lines.contains(&def_node.start_position().row) {
2596 let mut sig_parts = Vec::new();
2597 let mut parent = def_node.parent();
2598 while let Some(p) = parent {
2599 if p.kind() == "decorated_definition" {
2600 let mut dc = p.walk();
2601 if dc.goto_first_child() {
2602 loop {
2603 if dc.node().kind() == "decorator" {
2604 sig_parts.push(node_text(source, &dc.node()).to_string());
2605 }
2606 if !dc.goto_next_sibling() {
2607 break;
2608 }
2609 }
2610 }
2611 break;
2612 }
2613 parent = p.parent();
2614 }
2615 sig_parts.push(extract_signature(source, &def_node));
2616 Some(sig_parts.join("\n"))
2617 } else {
2618 Some(extract_signature(source, &def_node))
2619 };
2620
2621 symbols.push(Symbol {
2622 name: node_text(source, &name_node).to_string(),
2623 kind: SymbolKind::Class,
2624 range: node_range_with_decorators(&def_node, source, lang),
2625 signature: sig,
2626 scope_chain: scope.clone(),
2627 exported: false,
2628 parent: scope.last().cloned(),
2629 });
2630 }
2631 }
2632
2633 dedup_symbols(&mut symbols);
2634 Ok(symbols)
2635}
2636
2637fn rust_mod_scope_chain(node: &Node, source: &str) -> Vec<String> {
2638 let mut scopes = Vec::new();
2639 let mut current = node.parent();
2640
2641 while let Some(parent) = current {
2642 if parent.kind() == "mod_item" {
2643 if let Some(name_node) = parent.child_by_field_name("name") {
2644 scopes.push(node_text(source, &name_node).to_string());
2645 }
2646 }
2647 current = parent.parent();
2648 }
2649
2650 scopes.reverse();
2651 scopes
2652}
2653
2654pub(crate) const RUST_ENTRY_POINT_ATTRIBUTES: &[&str] = &[
2659 "tauri::command",
2660 "wasm_bindgen",
2661 "no_mangle",
2662 "export_name",
2663 "ctor",
2664 "dtor",
2665];
2666
2667#[derive(Debug, Clone, PartialEq, Eq)]
2668pub(crate) struct RustAttributeEntryPoint {
2669 pub name: String,
2670 pub scoped_name: String,
2671 pub attribute: &'static str,
2672}
2673
2674pub(crate) fn rust_attribute_entry_points<'a>(
2676 source: &str,
2677 root: Node<'a>,
2678) -> Vec<RustAttributeEntryPoint> {
2679 let bare_command_imported = rust_file_imports_tauri_command(source, root);
2680 let mut function_nodes = Vec::new();
2681 rust_collect_function_items(root, &mut function_nodes);
2682
2683 let mut entry_points = BTreeMap::new();
2684 for function_node in function_nodes {
2685 let Some(attribute) =
2686 rust_entry_point_attribute_for_function(source, &function_node, bare_command_imported)
2687 else {
2688 continue;
2689 };
2690 let Some((name, scoped_name)) = rust_function_identity(source, &function_node) else {
2691 continue;
2692 };
2693 entry_points
2694 .entry(scoped_name.clone())
2695 .or_insert(RustAttributeEntryPoint {
2696 name,
2697 scoped_name,
2698 attribute,
2699 });
2700 }
2701
2702 entry_points.into_values().collect()
2703}
2704
2705fn rust_collect_function_items<'a>(node: Node<'a>, function_nodes: &mut Vec<Node<'a>>) {
2706 if node.kind() == "function_item" {
2707 function_nodes.push(node);
2708 }
2709
2710 let mut cursor = node.walk();
2711 if cursor.goto_first_child() {
2712 loop {
2713 rust_collect_function_items(cursor.node(), function_nodes);
2714 if !cursor.goto_next_sibling() {
2715 break;
2716 }
2717 }
2718 }
2719}
2720
2721fn rust_function_identity(source: &str, function_node: &Node<'_>) -> Option<(String, String)> {
2722 let name_node = function_node.child_by_field_name("name")?;
2723 let name = node_text(source, &name_node).to_string();
2724 let declaration_list_owner = rust_function_declaration_list_owner(function_node);
2725
2726 match declaration_list_owner.as_ref().map(Node::kind) {
2727 Some("impl_item") => {
2728 let scope_name = rust_impl_scope_name(declaration_list_owner.as_ref().unwrap(), source);
2729 let scoped_name = if scope_name.is_empty() {
2730 name.clone()
2731 } else {
2732 format!("{scope_name}::{name}")
2733 };
2734 Some((name, scoped_name))
2735 }
2736 Some(owner_kind) if owner_kind != "mod_item" => None,
2737 _ => {
2738 let scope_chain = rust_mod_scope_chain(function_node, source);
2739 let scoped_name = if scope_chain.is_empty() {
2740 name.clone()
2741 } else {
2742 format!("{}::{name}", scope_chain.join("::"))
2743 };
2744 Some((name, scoped_name))
2745 }
2746 }
2747}
2748
2749fn rust_function_declaration_list_owner<'a>(function_node: &Node<'a>) -> Option<Node<'a>> {
2750 function_node
2751 .parent()
2752 .filter(|parent| parent.kind() == "declaration_list")
2753 .and_then(|parent| parent.parent())
2754}
2755
2756fn rust_impl_scope_name(impl_node: &Node, source: &str) -> String {
2757 let mut type_names: Vec<String> = Vec::new();
2758 let mut child_cursor = impl_node.walk();
2759 if child_cursor.goto_first_child() {
2760 loop {
2761 let child = child_cursor.node();
2762 if child.kind() == "type_identifier" || child.kind() == "generic_type" {
2763 type_names.push(node_text(source, &child).to_string());
2764 }
2765 if !child_cursor.goto_next_sibling() {
2766 break;
2767 }
2768 }
2769 }
2770
2771 if type_names.len() >= 2 {
2772 format!("{} for {}", type_names[0], type_names[1])
2773 } else if type_names.len() == 1 {
2774 type_names[0].clone()
2775 } else {
2776 String::new()
2777 }
2778}
2779
2780fn rust_entry_point_attribute_for_function(
2781 source: &str,
2782 function_node: &Node,
2783 bare_command_imported: bool,
2784) -> Option<&'static str> {
2785 let mut current = *function_node;
2786 while let Some(prev) = current.prev_sibling() {
2787 let kind = prev.kind();
2788 match kind {
2789 "attribute_item" => {
2790 if let Some(attribute) =
2791 rust_entry_point_attribute_from_node(source, &prev, bare_command_imported)
2792 {
2793 return Some(attribute);
2794 }
2795 current = prev;
2796 }
2797 "line_comment" if node_text(source, &prev).starts_with("///") => {
2798 current = prev;
2799 }
2800 "block_comment" if node_text(source, &prev).starts_with("/**") => {
2801 current = prev;
2802 }
2803 _ => break,
2804 }
2805 }
2806
2807 None
2808}
2809
2810fn rust_entry_point_attribute_from_node(
2811 source: &str,
2812 attribute_node: &Node,
2813 bare_command_imported: bool,
2814) -> Option<&'static str> {
2815 let text = node_text(source, attribute_node);
2816 let path = rust_attribute_path(text)?;
2817 rust_entry_point_attribute_for_path(path, bare_command_imported)
2818}
2819
2820fn rust_attribute_path(attribute_text: &str) -> Option<&str> {
2821 let inner = attribute_text
2822 .trim()
2823 .strip_prefix("#[")?
2824 .strip_suffix(']')?
2825 .trim();
2826 let effective = inner
2827 .strip_prefix("unsafe(")
2828 .and_then(|wrapped| wrapped.strip_suffix(')'))
2829 .map(str::trim)
2830 .unwrap_or(inner);
2831 effective
2832 .split(|ch: char| matches!(ch, '(' | '=' | ','))
2833 .next()
2834 .map(str::trim)
2835 .filter(|path| !path.is_empty())
2836}
2837
2838fn rust_entry_point_attribute_for_path(
2839 path: &str,
2840 bare_command_imported: bool,
2841) -> Option<&'static str> {
2842 let compact = path
2843 .chars()
2844 .filter(|ch| !ch.is_whitespace())
2845 .collect::<String>();
2846 if compact == "tauri::command" {
2847 return Some("tauri::command");
2848 }
2849 if compact == "command" && bare_command_imported {
2850 return Some("tauri::command");
2851 }
2852
2853 let last_segment = compact.rsplit("::").next().unwrap_or(compact.as_str());
2854 RUST_ENTRY_POINT_ATTRIBUTES
2855 .iter()
2856 .copied()
2857 .find(|attribute| *attribute != "tauri::command" && *attribute == last_segment)
2858}
2859
2860fn rust_file_imports_tauri_command(source: &str, root: Node<'_>) -> bool {
2861 let mut stack = vec![root];
2862 while let Some(node) = stack.pop() {
2863 if node.kind() == "use_declaration"
2864 && rust_use_declaration_imports_tauri_command(node_text(source, &node))
2865 {
2866 return true;
2867 }
2868
2869 let mut cursor = node.walk();
2870 if cursor.goto_first_child() {
2871 loop {
2872 stack.push(cursor.node());
2873 if !cursor.goto_next_sibling() {
2874 break;
2875 }
2876 }
2877 }
2878 }
2879
2880 false
2881}
2882
2883fn rust_use_declaration_imports_tauri_command(use_text: &str) -> bool {
2884 let compact = use_text
2885 .chars()
2886 .filter(|ch| !ch.is_whitespace())
2887 .collect::<String>();
2888 let Some(use_index) = compact.find("use") else {
2889 return false;
2890 };
2891 let path = compact[use_index + "use".len()..].trim_end_matches(';');
2892 if path == "tauri::command" {
2893 return true;
2894 }
2895
2896 let Some(inner) = path
2897 .strip_prefix("tauri::{")
2898 .and_then(|inner| inner.strip_suffix('}'))
2899 else {
2900 return false;
2901 };
2902
2903 split_rust_use_items(inner)
2904 .into_iter()
2905 .any(|item| item == "command" || item == "commandascommand")
2906}
2907
2908fn split_rust_use_items(inner: &str) -> Vec<&str> {
2909 let mut items = Vec::new();
2910 let mut depth = 0usize;
2911 let mut start = 0usize;
2912 for (idx, ch) in inner.char_indices() {
2913 match ch {
2914 '{' => depth += 1,
2915 '}' => depth = depth.saturating_sub(1),
2916 ',' if depth == 0 => {
2917 items.push(inner[start..idx].trim());
2918 start = idx + ch.len_utf8();
2919 }
2920 _ => {}
2921 }
2922 }
2923 if start <= inner.len() {
2924 let tail = inner[start..].trim();
2925 if !tail.is_empty() {
2926 items.push(tail);
2927 }
2928 }
2929 items
2930}
2931
2932fn extract_rs_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
2935 let lang = LangId::Rust;
2936 let is_pub = |node: &Node| -> bool {
2937 let mut child_cursor = node.walk();
2938 if child_cursor.goto_first_child() {
2939 loop {
2940 if child_cursor.node().kind() == "visibility_modifier" {
2941 return true;
2942 }
2943 if !child_cursor.goto_next_sibling() {
2944 break;
2945 }
2946 }
2947 }
2948 false
2949 };
2950
2951 let item_symbol = |node: Node<'_>, kind: SymbolKind| -> Option<Symbol> {
2952 let name_node = node.child_by_field_name("name")?;
2953 Some(Symbol {
2954 name: node_text(source, &name_node).to_string(),
2955 kind,
2956 range: node_range_with_decorators(&node, source, lang),
2957 signature: Some(extract_signature(source, &node)),
2958 scope_chain: Vec::new(),
2959 exported: is_pub(&node),
2960 parent: None,
2961 })
2962 };
2963
2964 let mut symbols = Vec::new();
2965 let mut pending = vec![*root];
2966 while let Some(node) = pending.pop() {
2967 match node.kind() {
2968 "function_item" => {
2969 let declaration_list_owner = node
2970 .parent()
2971 .filter(|parent| parent.kind() == "declaration_list")
2972 .and_then(|parent| parent.parent());
2973 let in_non_module_declaration_list = declaration_list_owner
2974 .as_ref()
2975 .is_some_and(|owner| owner.kind() != "mod_item");
2976
2977 if !in_non_module_declaration_list {
2978 if let Some(name_node) = node.child_by_field_name("name") {
2979 let scope_chain = rust_mod_scope_chain(&node, source);
2980 symbols.push(Symbol {
2981 name: node_text(source, &name_node).to_string(),
2982 kind: SymbolKind::Function,
2983 range: node_range_with_decorators(&node, source, lang),
2984 signature: Some(extract_signature(source, &node)),
2985 scope_chain: scope_chain.clone(),
2986 exported: is_pub(&node),
2987 parent: scope_chain.last().cloned(),
2988 });
2989 }
2990 }
2991 }
2992 "struct_item" => {
2993 if let Some(symbol) = item_symbol(node, SymbolKind::Struct) {
2994 symbols.push(symbol);
2995 }
2996 }
2997 "enum_item" => {
2998 if let Some(symbol) = item_symbol(node, SymbolKind::Enum) {
2999 symbols.push(symbol);
3000 }
3001 }
3002 "trait_item" => {
3003 if let Some(symbol) = item_symbol(node, SymbolKind::Interface) {
3004 symbols.push(symbol);
3005 }
3006 }
3007 "impl_item" => {
3008 let mut type_names = Vec::new();
3009 let mut child_cursor = node.walk();
3010 if child_cursor.goto_first_child() {
3011 loop {
3012 let child = child_cursor.node();
3013 if child.kind() == "type_identifier" || child.kind() == "generic_type" {
3014 type_names.push(node_text(source, &child).to_string());
3015 }
3016 if !child_cursor.goto_next_sibling() {
3017 break;
3018 }
3019 }
3020 }
3021
3022 let scope_name = if type_names.len() >= 2 {
3023 format!("{} for {}", type_names[0], type_names[1])
3024 } else {
3025 type_names.first().cloned().unwrap_or_default()
3026 };
3027 let parent_name = type_names.last().cloned().unwrap_or_default();
3028
3029 let mut child_cursor = node.walk();
3030 if child_cursor.goto_first_child() {
3031 loop {
3032 let child = child_cursor.node();
3033 if child.kind() == "declaration_list" {
3034 let mut method_cursor = child.walk();
3035 if method_cursor.goto_first_child() {
3036 loop {
3037 let method = method_cursor.node();
3038 if method.kind() == "function_item" {
3039 if let Some(name_node) = method.child_by_field_name("name")
3040 {
3041 symbols.push(Symbol {
3042 name: node_text(source, &name_node).to_string(),
3043 kind: SymbolKind::Method,
3044 range: node_range_with_decorators(
3045 &method, source, lang,
3046 ),
3047 signature: Some(extract_signature(source, &method)),
3048 scope_chain: if scope_name.is_empty() {
3049 Vec::new()
3050 } else {
3051 vec![scope_name.clone()]
3052 },
3053 exported: is_pub(&method),
3054 parent: if parent_name.is_empty() {
3055 None
3056 } else {
3057 Some(parent_name.clone())
3058 },
3059 });
3060 }
3061 }
3062 if !method_cursor.goto_next_sibling() {
3063 break;
3064 }
3065 }
3066 }
3067 }
3068 if !child_cursor.goto_next_sibling() {
3069 break;
3070 }
3071 }
3072 }
3073 }
3074 _ => {}
3075 }
3076
3077 let mut child_cursor = node.walk();
3078 let mut children = Vec::new();
3079 if child_cursor.goto_first_child() {
3080 loop {
3081 children.push(child_cursor.node());
3082 if !child_cursor.goto_next_sibling() {
3083 break;
3084 }
3085 }
3086 }
3087 pending.extend(children.into_iter().rev());
3088 }
3089
3090 dedup_symbols(&mut symbols);
3091 Ok(symbols)
3092}
3093
3094fn extract_go_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
3098 let lang = LangId::Go;
3099 let capture_names = query.capture_names();
3100
3101 let is_go_exported = |name: &str| -> bool {
3102 name.chars()
3103 .next()
3104 .map(|c| c.is_uppercase())
3105 .unwrap_or(false)
3106 };
3107
3108 let mut symbols = Vec::new();
3109 let mut cursor = QueryCursor::new();
3110 let mut matches = cursor.matches(query, *root, source.as_bytes());
3111
3112 while let Some(m) = {
3113 matches.advance();
3114 matches.get()
3115 } {
3116 let mut fn_name_node = None;
3117 let mut fn_def_node = None;
3118 let mut method_name_node = None;
3119 let mut method_def_node = None;
3120 let mut type_name_node = None;
3121 let mut type_body_node = None;
3122 let mut type_def_node = None;
3123
3124 for cap in m.captures {
3125 let Some(&name) = capture_names.get(cap.index as usize) else {
3126 continue;
3127 };
3128 match name {
3129 "fn.name" => fn_name_node = Some(cap.node),
3130 "fn.def" => fn_def_node = Some(cap.node),
3131 "method.name" => method_name_node = Some(cap.node),
3132 "method.def" => method_def_node = Some(cap.node),
3133 "type.name" => type_name_node = Some(cap.node),
3134 "type.body" => type_body_node = Some(cap.node),
3135 "type.def" => type_def_node = Some(cap.node),
3136 _ => {}
3137 }
3138 }
3139
3140 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
3142 let name = node_text(source, &name_node).to_string();
3143 symbols.push(Symbol {
3144 exported: is_go_exported(&name),
3145 name,
3146 kind: SymbolKind::Function,
3147 range: node_range_with_decorators(&def_node, source, lang),
3148 signature: Some(extract_signature(source, &def_node)),
3149 scope_chain: vec![],
3150 parent: None,
3151 });
3152 }
3153
3154 if let (Some(name_node), Some(def_node)) = (method_name_node, method_def_node) {
3156 let name = node_text(source, &name_node).to_string();
3157
3158 let receiver_type = extract_go_receiver_type(&def_node, source);
3160 let scope_chain = if let Some(ref rt) = receiver_type {
3161 vec![rt.clone()]
3162 } else {
3163 vec![]
3164 };
3165
3166 symbols.push(Symbol {
3167 exported: is_go_exported(&name),
3168 name,
3169 kind: SymbolKind::Method,
3170 range: node_range_with_decorators(&def_node, source, lang),
3171 signature: Some(extract_signature(source, &def_node)),
3172 scope_chain,
3173 parent: receiver_type,
3174 });
3175 }
3176
3177 if let (Some(name_node), Some(body_node), Some(def_node)) =
3179 (type_name_node, type_body_node, type_def_node)
3180 {
3181 let name = node_text(source, &name_node).to_string();
3182 let kind = match body_node.kind() {
3183 "struct_type" => SymbolKind::Struct,
3184 "interface_type" => SymbolKind::Interface,
3185 _ => SymbolKind::TypeAlias,
3186 };
3187
3188 symbols.push(Symbol {
3189 exported: is_go_exported(&name),
3190 name,
3191 kind,
3192 range: node_range_with_decorators(&def_node, source, lang),
3193 signature: Some(extract_signature(source, &def_node)),
3194 scope_chain: vec![],
3195 parent: None,
3196 });
3197 }
3198 }
3199
3200 dedup_symbols(&mut symbols);
3201 Ok(symbols)
3202}
3203
3204fn extract_go_receiver_type(method_node: &Node, source: &str) -> Option<String> {
3207 let mut child_cursor = method_node.walk();
3209 if child_cursor.goto_first_child() {
3210 loop {
3211 let child = child_cursor.node();
3212 if child.kind() == "parameter_list" {
3213 return find_type_identifier_recursive(&child, source);
3215 }
3216 if !child_cursor.goto_next_sibling() {
3217 break;
3218 }
3219 }
3220 }
3221 None
3222}
3223
3224fn split_scope_text(text: &str, separator: &str) -> Vec<String> {
3225 text.split(separator)
3226 .map(str::trim)
3227 .filter(|segment| !segment.is_empty())
3228 .map(ToString::to_string)
3229 .collect()
3230}
3231
3232fn last_scope_segment(text: &str, separator: &str) -> String {
3233 split_scope_text(text, separator)
3234 .pop()
3235 .unwrap_or_else(|| text.trim().to_string())
3236}
3237
3238fn zig_container_scope_chain(node: &Node, source: &str) -> Vec<String> {
3239 let mut chain = Vec::new();
3240 let mut current = node.parent();
3241
3242 while let Some(parent) = current {
3243 if matches!(
3244 parent.kind(),
3245 "struct_declaration" | "enum_declaration" | "union_declaration" | "opaque_declaration"
3246 ) {
3247 if let Some(container) = parent.parent() {
3248 if container.kind() == "variable_declaration" {
3249 let mut cursor = container.walk();
3250 if cursor.goto_first_child() {
3251 loop {
3252 let child = cursor.node();
3253 if child.kind() == "identifier" {
3254 chain.push(node_text(source, &child).to_string());
3255 break;
3256 }
3257 if !cursor.goto_next_sibling() {
3258 break;
3259 }
3260 }
3261 }
3262 }
3263 }
3264 }
3265 current = parent.parent();
3266 }
3267
3268 chain.reverse();
3269 chain
3270}
3271
3272fn csharp_scope_chain(node: &Node, source: &str) -> Vec<String> {
3273 let mut chain = Vec::new();
3274 let mut current = node.parent();
3275
3276 while let Some(parent) = current {
3277 match parent.kind() {
3278 "namespace_declaration" | "file_scoped_namespace_declaration" => {
3279 if let Some(name_node) = parent.child_by_field_name("name") {
3280 chain.push(node_text(source, &name_node).to_string());
3281 }
3282 }
3283 "class_declaration"
3284 | "interface_declaration"
3285 | "struct_declaration"
3286 | "record_declaration" => {
3287 if let Some(name_node) = parent.child_by_field_name("name") {
3288 chain.push(node_text(source, &name_node).to_string());
3289 }
3290 }
3291 _ => {}
3292 }
3293 current = parent.parent();
3294 }
3295
3296 chain.reverse();
3297 chain
3298}
3299
3300fn cpp_parent_scope_chain(node: &Node, source: &str) -> Vec<String> {
3301 let mut chain = Vec::new();
3302 let mut current = node.parent();
3303
3304 while let Some(parent) = current {
3305 match parent.kind() {
3306 "namespace_definition" => {
3307 if let Some(name_node) = parent.child_by_field_name("name") {
3308 chain.push(node_text(source, &name_node).to_string());
3309 }
3310 }
3311 "class_specifier" | "struct_specifier" => {
3312 if let Some(name_node) = parent.child_by_field_name("name") {
3313 chain.push(last_scope_segment(node_text(source, &name_node), "::"));
3314 }
3315 }
3316 _ => {}
3317 }
3318 current = parent.parent();
3319 }
3320
3321 chain.reverse();
3322 chain
3323}
3324
3325fn template_signature(source: &str, template_node: &Node, item_node: &Node) -> String {
3326 format!(
3327 "{}\n{}",
3328 extract_signature(source, template_node),
3329 extract_signature(source, item_node)
3330 )
3331}
3332
3333fn extract_c_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
3335 let lang = LangId::C;
3336 let capture_names = query.capture_names();
3337
3338 let mut symbols = Vec::new();
3339 let mut cursor = QueryCursor::new();
3340 let mut matches = cursor.matches(query, *root, source.as_bytes());
3341
3342 while let Some(m) = {
3343 matches.advance();
3344 matches.get()
3345 } {
3346 let mut fn_name_node = None;
3347 let mut fn_def_node = None;
3348 let mut struct_name_node = None;
3349 let mut struct_def_node = None;
3350 let mut enum_name_node = None;
3351 let mut enum_def_node = None;
3352 let mut type_name_node = None;
3353 let mut type_def_node = None;
3354 let mut macro_name_node = None;
3355 let mut macro_def_node = None;
3356
3357 for cap in m.captures {
3358 let Some(&name) = capture_names.get(cap.index as usize) else {
3359 continue;
3360 };
3361 match name {
3362 "fn.name" => fn_name_node = Some(cap.node),
3363 "fn.def" => fn_def_node = Some(cap.node),
3364 "struct.name" => struct_name_node = Some(cap.node),
3365 "struct.def" => struct_def_node = Some(cap.node),
3366 "enum.name" => enum_name_node = Some(cap.node),
3367 "enum.def" => enum_def_node = Some(cap.node),
3368 "type.name" => type_name_node = Some(cap.node),
3369 "type.def" => type_def_node = Some(cap.node),
3370 "macro.name" => macro_name_node = Some(cap.node),
3371 "macro.def" => macro_def_node = Some(cap.node),
3372 _ => {}
3373 }
3374 }
3375
3376 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
3377 symbols.push(Symbol {
3378 name: node_text(source, &name_node).to_string(),
3379 kind: SymbolKind::Function,
3380 range: node_range_with_decorators(&def_node, source, lang),
3381 signature: Some(extract_signature(source, &def_node)),
3382 scope_chain: vec![],
3383 exported: false,
3384 parent: None,
3385 });
3386 }
3387
3388 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
3389 symbols.push(Symbol {
3390 name: node_text(source, &name_node).to_string(),
3391 kind: SymbolKind::Struct,
3392 range: node_range_with_decorators(&def_node, source, lang),
3393 signature: Some(extract_signature(source, &def_node)),
3394 scope_chain: vec![],
3395 exported: false,
3396 parent: None,
3397 });
3398 }
3399
3400 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
3401 symbols.push(Symbol {
3402 name: node_text(source, &name_node).to_string(),
3403 kind: SymbolKind::Enum,
3404 range: node_range_with_decorators(&def_node, source, lang),
3405 signature: Some(extract_signature(source, &def_node)),
3406 scope_chain: vec![],
3407 exported: false,
3408 parent: None,
3409 });
3410 }
3411
3412 if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
3413 symbols.push(Symbol {
3414 name: node_text(source, &name_node).to_string(),
3415 kind: SymbolKind::TypeAlias,
3416 range: node_range_with_decorators(&def_node, source, lang),
3417 signature: Some(extract_signature(source, &def_node)),
3418 scope_chain: vec![],
3419 exported: false,
3420 parent: None,
3421 });
3422 }
3423
3424 if let (Some(name_node), Some(def_node)) = (macro_name_node, macro_def_node) {
3425 symbols.push(Symbol {
3426 name: node_text(source, &name_node).to_string(),
3427 kind: SymbolKind::Variable,
3428 range: node_range(&def_node),
3429 signature: Some(extract_signature(source, &def_node)),
3430 scope_chain: vec![],
3431 exported: false,
3432 parent: None,
3433 });
3434 }
3435 }
3436
3437 dedup_symbols(&mut symbols);
3438 Ok(symbols)
3439}
3440
3441fn cpp_function_kind(source: &str, definition: &Node, lang: LangId) -> SymbolKind {
3442 let is_cuda_kernel = lang == LangId::Cuda
3443 && node_text(source, definition)
3444 .split(|character: char| !(character.is_ascii_alphanumeric() || character == '_'))
3445 .any(|token| token == "__global__");
3446 if is_cuda_kernel {
3447 SymbolKind::Kernel
3448 } else {
3449 SymbolKind::Function
3450 }
3451}
3452
3453fn extract_cpp_symbols(
3455 source: &str,
3456 root: &Node,
3457 query: &Query,
3458 lang: LangId,
3459) -> Result<Vec<Symbol>, AftError> {
3460 let capture_names = query.capture_names();
3461
3462 let mut type_names = HashSet::new();
3463 {
3464 let mut cursor = QueryCursor::new();
3465 let mut matches = cursor.matches(query, *root, source.as_bytes());
3466 while let Some(m) = {
3467 matches.advance();
3468 matches.get()
3469 } {
3470 for cap in m.captures {
3471 let Some(&name) = capture_names.get(cap.index as usize) else {
3472 continue;
3473 };
3474 match name {
3475 "class.name"
3476 | "struct.name"
3477 | "template.class.name"
3478 | "template.struct.name" => {
3479 type_names.insert(last_scope_segment(node_text(source, &cap.node), "::"));
3480 }
3481 _ => {}
3482 }
3483 }
3484 }
3485 }
3486
3487 let mut symbols = Vec::new();
3488 let mut cursor = QueryCursor::new();
3489 let mut matches = cursor.matches(query, *root, source.as_bytes());
3490
3491 while let Some(m) = {
3492 matches.advance();
3493 matches.get()
3494 } {
3495 let mut fn_name_node = None;
3496 let mut fn_def_node = None;
3497 let mut method_name_node = None;
3498 let mut method_def_node = None;
3499 let mut qual_scope_node = None;
3500 let mut qual_name_node = None;
3501 let mut qual_def_node = None;
3502 let mut class_name_node = None;
3503 let mut class_def_node = None;
3504 let mut struct_name_node = None;
3505 let mut struct_def_node = None;
3506 let mut enum_name_node = None;
3507 let mut enum_def_node = None;
3508 let mut namespace_name_node = None;
3509 let mut namespace_def_node = None;
3510 let mut template_class_name_node = None;
3511 let mut template_class_def_node = None;
3512 let mut template_class_item_node = None;
3513 let mut template_struct_name_node = None;
3514 let mut template_struct_def_node = None;
3515 let mut template_struct_item_node = None;
3516 let mut template_fn_name_node = None;
3517 let mut template_fn_def_node = None;
3518 let mut template_fn_item_node = None;
3519 let mut template_qual_scope_node = None;
3520 let mut template_qual_name_node = None;
3521 let mut template_qual_def_node = None;
3522 let mut template_qual_item_node = None;
3523
3524 for cap in m.captures {
3525 let Some(&name) = capture_names.get(cap.index as usize) else {
3526 continue;
3527 };
3528 match name {
3529 "fn.name" => fn_name_node = Some(cap.node),
3530 "fn.def" => fn_def_node = Some(cap.node),
3531 "method.name" => method_name_node = Some(cap.node),
3532 "method.def" => method_def_node = Some(cap.node),
3533 "qual.scope" => qual_scope_node = Some(cap.node),
3534 "qual.name" => qual_name_node = Some(cap.node),
3535 "qual.def" => qual_def_node = Some(cap.node),
3536 "class.name" => class_name_node = Some(cap.node),
3537 "class.def" => class_def_node = Some(cap.node),
3538 "struct.name" => struct_name_node = Some(cap.node),
3539 "struct.def" => struct_def_node = Some(cap.node),
3540 "enum.name" => enum_name_node = Some(cap.node),
3541 "enum.def" => enum_def_node = Some(cap.node),
3542 "namespace.name" => namespace_name_node = Some(cap.node),
3543 "namespace.def" => namespace_def_node = Some(cap.node),
3544 "template.class.name" => template_class_name_node = Some(cap.node),
3545 "template.class.def" => template_class_def_node = Some(cap.node),
3546 "template.class.item" => template_class_item_node = Some(cap.node),
3547 "template.struct.name" => template_struct_name_node = Some(cap.node),
3548 "template.struct.def" => template_struct_def_node = Some(cap.node),
3549 "template.struct.item" => template_struct_item_node = Some(cap.node),
3550 "template.fn.name" => template_fn_name_node = Some(cap.node),
3551 "template.fn.def" => template_fn_def_node = Some(cap.node),
3552 "template.fn.item" => template_fn_item_node = Some(cap.node),
3553 "template.qual.scope" => template_qual_scope_node = Some(cap.node),
3554 "template.qual.name" => template_qual_name_node = Some(cap.node),
3555 "template.qual.def" => template_qual_def_node = Some(cap.node),
3556 "template.qual.item" => template_qual_item_node = Some(cap.node),
3557 _ => {}
3558 }
3559 }
3560
3561 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
3562 let in_template = def_node
3563 .parent()
3564 .map(|parent| parent.kind() == "template_declaration")
3565 .unwrap_or(false);
3566 if !in_template {
3567 let scope_chain = cpp_parent_scope_chain(&def_node, source);
3568 symbols.push(Symbol {
3569 name: node_text(source, &name_node).to_string(),
3570 kind: cpp_function_kind(source, &def_node, lang),
3571 range: node_range_with_decorators(&def_node, source, lang),
3572 signature: Some(extract_signature(source, &def_node)),
3573 scope_chain: scope_chain.clone(),
3574 exported: false,
3575 parent: scope_chain.last().cloned(),
3576 });
3577 }
3578 }
3579
3580 if let (Some(name_node), Some(def_node)) = (method_name_node, method_def_node) {
3581 let scope_chain = cpp_parent_scope_chain(&def_node, source);
3582 symbols.push(Symbol {
3583 name: node_text(source, &name_node).to_string(),
3584 kind: SymbolKind::Method,
3585 range: node_range_with_decorators(&def_node, source, lang),
3586 signature: Some(extract_signature(source, &def_node)),
3587 scope_chain: scope_chain.clone(),
3588 exported: false,
3589 parent: scope_chain.last().cloned(),
3590 });
3591 }
3592
3593 if let (Some(scope_node), Some(name_node), Some(def_node)) =
3594 (qual_scope_node, qual_name_node, qual_def_node)
3595 {
3596 let in_template = def_node
3597 .parent()
3598 .map(|parent| parent.kind() == "template_declaration")
3599 .unwrap_or(false);
3600 if !in_template {
3601 let scope_text = node_text(source, &scope_node);
3602 let scope_chain = split_scope_text(scope_text, "::");
3603 let parent = scope_chain.last().cloned();
3604 let kind = if parent
3605 .as_ref()
3606 .map(|segment| type_names.contains(segment))
3607 .unwrap_or(false)
3608 {
3609 SymbolKind::Method
3610 } else {
3611 SymbolKind::Function
3612 };
3613
3614 symbols.push(Symbol {
3615 name: node_text(source, &name_node).to_string(),
3616 kind,
3617 range: node_range_with_decorators(&def_node, source, lang),
3618 signature: Some(extract_signature(source, &def_node)),
3619 scope_chain,
3620 exported: false,
3621 parent,
3622 });
3623 }
3624 }
3625
3626 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
3627 let in_template = def_node
3628 .parent()
3629 .map(|parent| parent.kind() == "template_declaration")
3630 .unwrap_or(false);
3631 if !in_template {
3632 let scope_chain = cpp_parent_scope_chain(&def_node, source);
3633 let name = last_scope_segment(node_text(source, &name_node), "::");
3634 symbols.push(Symbol {
3635 name: name.clone(),
3636 kind: SymbolKind::Class,
3637 range: node_range_with_decorators(&def_node, source, lang),
3638 signature: Some(extract_signature(source, &def_node)),
3639 scope_chain: scope_chain.clone(),
3640 exported: false,
3641 parent: scope_chain.last().cloned(),
3642 });
3643 }
3644 }
3645
3646 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
3647 let in_template = def_node
3648 .parent()
3649 .map(|parent| parent.kind() == "template_declaration")
3650 .unwrap_or(false);
3651 if !in_template {
3652 let scope_chain = cpp_parent_scope_chain(&def_node, source);
3653 let name = last_scope_segment(node_text(source, &name_node), "::");
3654 symbols.push(Symbol {
3655 name: name.clone(),
3656 kind: SymbolKind::Struct,
3657 range: node_range_with_decorators(&def_node, source, lang),
3658 signature: Some(extract_signature(source, &def_node)),
3659 scope_chain: scope_chain.clone(),
3660 exported: false,
3661 parent: scope_chain.last().cloned(),
3662 });
3663 }
3664 }
3665
3666 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
3667 let scope_chain = cpp_parent_scope_chain(&def_node, source);
3668 let name = last_scope_segment(node_text(source, &name_node), "::");
3669 symbols.push(Symbol {
3670 name: name.clone(),
3671 kind: SymbolKind::Enum,
3672 range: node_range_with_decorators(&def_node, source, lang),
3673 signature: Some(extract_signature(source, &def_node)),
3674 scope_chain: scope_chain.clone(),
3675 exported: false,
3676 parent: scope_chain.last().cloned(),
3677 });
3678 }
3679
3680 if let (Some(name_node), Some(def_node)) = (namespace_name_node, namespace_def_node) {
3681 let scope_chain = cpp_parent_scope_chain(&def_node, source);
3682 symbols.push(Symbol {
3683 name: node_text(source, &name_node).to_string(),
3684 kind: SymbolKind::TypeAlias,
3685 range: node_range_with_decorators(&def_node, source, lang),
3686 signature: Some(extract_signature(source, &def_node)),
3687 scope_chain: scope_chain.clone(),
3688 exported: false,
3689 parent: scope_chain.last().cloned(),
3690 });
3691 }
3692
3693 if let (Some(name_node), Some(def_node), Some(item_node)) = (
3694 template_class_name_node,
3695 template_class_def_node,
3696 template_class_item_node,
3697 ) {
3698 let scope_chain = cpp_parent_scope_chain(&def_node, source);
3699 let name = last_scope_segment(node_text(source, &name_node), "::");
3700 symbols.push(Symbol {
3701 name: name.clone(),
3702 kind: SymbolKind::Class,
3703 range: node_range_with_decorators(&def_node, source, lang),
3704 signature: Some(template_signature(source, &def_node, &item_node)),
3705 scope_chain: scope_chain.clone(),
3706 exported: false,
3707 parent: scope_chain.last().cloned(),
3708 });
3709 }
3710
3711 if let (Some(name_node), Some(def_node), Some(item_node)) = (
3712 template_struct_name_node,
3713 template_struct_def_node,
3714 template_struct_item_node,
3715 ) {
3716 let scope_chain = cpp_parent_scope_chain(&def_node, source);
3717 let name = last_scope_segment(node_text(source, &name_node), "::");
3718 symbols.push(Symbol {
3719 name: name.clone(),
3720 kind: SymbolKind::Struct,
3721 range: node_range_with_decorators(&def_node, source, lang),
3722 signature: Some(template_signature(source, &def_node, &item_node)),
3723 scope_chain: scope_chain.clone(),
3724 exported: false,
3725 parent: scope_chain.last().cloned(),
3726 });
3727 }
3728
3729 if let (Some(name_node), Some(def_node), Some(item_node)) = (
3730 template_fn_name_node,
3731 template_fn_def_node,
3732 template_fn_item_node,
3733 ) {
3734 let scope_chain = cpp_parent_scope_chain(&def_node, source);
3735 symbols.push(Symbol {
3736 name: node_text(source, &name_node).to_string(),
3737 kind: SymbolKind::Function,
3738 range: node_range_with_decorators(&def_node, source, lang),
3739 signature: Some(template_signature(source, &def_node, &item_node)),
3740 scope_chain: scope_chain.clone(),
3741 exported: false,
3742 parent: scope_chain.last().cloned(),
3743 });
3744 }
3745
3746 if let (Some(scope_node), Some(name_node), Some(def_node), Some(item_node)) = (
3747 template_qual_scope_node,
3748 template_qual_name_node,
3749 template_qual_def_node,
3750 template_qual_item_node,
3751 ) {
3752 let scope_chain = split_scope_text(node_text(source, &scope_node), "::");
3753 let parent = scope_chain.last().cloned();
3754 let kind = if parent
3755 .as_ref()
3756 .map(|segment| type_names.contains(segment))
3757 .unwrap_or(false)
3758 {
3759 SymbolKind::Method
3760 } else {
3761 SymbolKind::Function
3762 };
3763
3764 symbols.push(Symbol {
3765 name: node_text(source, &name_node).to_string(),
3766 kind,
3767 range: node_range_with_decorators(&def_node, source, lang),
3768 signature: Some(template_signature(source, &def_node, &item_node)),
3769 scope_chain,
3770 exported: false,
3771 parent,
3772 });
3773 }
3774 }
3775
3776 dedup_symbols(&mut symbols);
3777 Ok(symbols)
3778}
3779
3780fn extract_zig_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
3782 let lang = LangId::Zig;
3783 let capture_names = query.capture_names();
3784
3785 let mut symbols = Vec::new();
3786 let mut cursor = QueryCursor::new();
3787 let mut matches = cursor.matches(query, *root, source.as_bytes());
3788
3789 while let Some(m) = {
3790 matches.advance();
3791 matches.get()
3792 } {
3793 let mut fn_name_node = None;
3794 let mut fn_def_node = None;
3795 let mut struct_name_node = None;
3796 let mut struct_def_node = None;
3797 let mut enum_name_node = None;
3798 let mut enum_def_node = None;
3799 let mut union_name_node = None;
3800 let mut union_def_node = None;
3801 let mut const_name_node = None;
3802 let mut const_def_node = None;
3803 let mut test_name_node = None;
3804 let mut test_def_node = None;
3805
3806 for cap in m.captures {
3807 let Some(&name) = capture_names.get(cap.index as usize) else {
3808 continue;
3809 };
3810 match name {
3811 "fn.name" => fn_name_node = Some(cap.node),
3812 "fn.def" => fn_def_node = Some(cap.node),
3813 "struct.name" => struct_name_node = Some(cap.node),
3814 "struct.def" => struct_def_node = Some(cap.node),
3815 "enum.name" => enum_name_node = Some(cap.node),
3816 "enum.def" => enum_def_node = Some(cap.node),
3817 "union.name" => union_name_node = Some(cap.node),
3818 "union.def" => union_def_node = Some(cap.node),
3819 "const.name" => const_name_node = Some(cap.node),
3820 "const.def" => const_def_node = Some(cap.node),
3821 "test.name" => test_name_node = Some(cap.node),
3822 "test.def" => test_def_node = Some(cap.node),
3823 _ => {}
3824 }
3825 }
3826
3827 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
3828 let scope_chain = zig_container_scope_chain(&def_node, source);
3829 let kind = if scope_chain.is_empty() {
3830 SymbolKind::Function
3831 } else {
3832 SymbolKind::Method
3833 };
3834 symbols.push(Symbol {
3835 name: node_text(source, &name_node).to_string(),
3836 kind,
3837 range: node_range_with_decorators(&def_node, source, lang),
3838 signature: Some(extract_signature(source, &def_node)),
3839 scope_chain: scope_chain.clone(),
3840 exported: false,
3841 parent: scope_chain.last().cloned(),
3842 });
3843 }
3844
3845 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
3846 symbols.push(Symbol {
3847 name: node_text(source, &name_node).to_string(),
3848 kind: SymbolKind::Struct,
3849 range: node_range_with_decorators(&def_node, source, lang),
3850 signature: Some(extract_signature(source, &def_node)),
3851 scope_chain: vec![],
3852 exported: false,
3853 parent: None,
3854 });
3855 }
3856
3857 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
3858 symbols.push(Symbol {
3859 name: node_text(source, &name_node).to_string(),
3860 kind: SymbolKind::Enum,
3861 range: node_range_with_decorators(&def_node, source, lang),
3862 signature: Some(extract_signature(source, &def_node)),
3863 scope_chain: vec![],
3864 exported: false,
3865 parent: None,
3866 });
3867 }
3868
3869 if let (Some(name_node), Some(def_node)) = (union_name_node, union_def_node) {
3870 symbols.push(Symbol {
3871 name: node_text(source, &name_node).to_string(),
3872 kind: SymbolKind::TypeAlias,
3873 range: node_range_with_decorators(&def_node, source, lang),
3874 signature: Some(extract_signature(source, &def_node)),
3875 scope_chain: vec![],
3876 exported: false,
3877 parent: None,
3878 });
3879 }
3880
3881 if let (Some(name_node), Some(def_node)) = (const_name_node, const_def_node) {
3882 let signature = extract_signature(source, &def_node);
3883 let is_container = signature.contains("= struct")
3884 || signature.contains("= enum")
3885 || signature.contains("= union")
3886 || signature.contains("= opaque");
3887 let is_const = signature.trim_start().starts_with("const ");
3888 let name = node_text(source, &name_node).to_string();
3889 let already_captured = symbols.iter().any(|symbol| symbol.name == name);
3890 if is_const && !is_container && !already_captured {
3891 symbols.push(Symbol {
3892 name,
3893 kind: SymbolKind::Variable,
3894 range: node_range_with_decorators(&def_node, source, lang),
3895 signature: Some(signature),
3896 scope_chain: vec![],
3897 exported: false,
3898 parent: None,
3899 });
3900 }
3901 }
3902
3903 if let (Some(name_node), Some(def_node)) = (test_name_node, test_def_node) {
3904 let scope_chain = zig_container_scope_chain(&def_node, source);
3905 symbols.push(Symbol {
3906 name: node_text(source, &name_node).trim_matches('"').to_string(),
3907 kind: SymbolKind::Function,
3908 range: node_range_with_decorators(&def_node, source, lang),
3909 signature: Some(extract_signature(source, &def_node)),
3910 scope_chain: scope_chain.clone(),
3911 exported: false,
3912 parent: scope_chain.last().cloned(),
3913 });
3914 }
3915 }
3916
3917 dedup_symbols(&mut symbols);
3918 Ok(symbols)
3919}
3920
3921fn extract_csharp_symbols(
3923 source: &str,
3924 root: &Node,
3925 query: &Query,
3926) -> Result<Vec<Symbol>, AftError> {
3927 let lang = LangId::CSharp;
3928 let capture_names = query.capture_names();
3929
3930 let mut symbols = Vec::new();
3931 let mut cursor = QueryCursor::new();
3932 let mut matches = cursor.matches(query, *root, source.as_bytes());
3933
3934 while let Some(m) = {
3935 matches.advance();
3936 matches.get()
3937 } {
3938 let mut class_name_node = None;
3939 let mut class_def_node = None;
3940 let mut interface_name_node = None;
3941 let mut interface_def_node = None;
3942 let mut struct_name_node = None;
3943 let mut struct_def_node = None;
3944 let mut enum_name_node = None;
3945 let mut enum_def_node = None;
3946 let mut method_name_node = None;
3947 let mut method_def_node = None;
3948 let mut property_name_node = None;
3949 let mut property_def_node = None;
3950 let mut namespace_name_node = None;
3951 let mut namespace_def_node = None;
3952
3953 for cap in m.captures {
3954 let Some(&name) = capture_names.get(cap.index as usize) else {
3955 continue;
3956 };
3957 match name {
3958 "class.name" => class_name_node = Some(cap.node),
3959 "class.def" => class_def_node = Some(cap.node),
3960 "interface.name" => interface_name_node = Some(cap.node),
3961 "interface.def" => interface_def_node = Some(cap.node),
3962 "struct.name" => struct_name_node = Some(cap.node),
3963 "struct.def" => struct_def_node = Some(cap.node),
3964 "enum.name" => enum_name_node = Some(cap.node),
3965 "enum.def" => enum_def_node = Some(cap.node),
3966 "method.name" => method_name_node = Some(cap.node),
3967 "method.def" => method_def_node = Some(cap.node),
3968 "property.name" => property_name_node = Some(cap.node),
3969 "property.def" => property_def_node = Some(cap.node),
3970 "namespace.name" => namespace_name_node = Some(cap.node),
3971 "namespace.def" => namespace_def_node = Some(cap.node),
3972 _ => {}
3973 }
3974 }
3975
3976 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
3977 let scope_chain = csharp_scope_chain(&def_node, source);
3978 symbols.push(Symbol {
3979 name: node_text(source, &name_node).to_string(),
3980 kind: SymbolKind::Class,
3981 range: node_range_with_decorators(&def_node, source, lang),
3982 signature: Some(extract_signature(source, &def_node)),
3983 scope_chain: scope_chain.clone(),
3984 exported: false,
3985 parent: scope_chain.last().cloned(),
3986 });
3987 }
3988
3989 if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
3990 let scope_chain = csharp_scope_chain(&def_node, source);
3991 symbols.push(Symbol {
3992 name: node_text(source, &name_node).to_string(),
3993 kind: SymbolKind::Interface,
3994 range: node_range_with_decorators(&def_node, source, lang),
3995 signature: Some(extract_signature(source, &def_node)),
3996 scope_chain: scope_chain.clone(),
3997 exported: false,
3998 parent: scope_chain.last().cloned(),
3999 });
4000 }
4001
4002 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
4003 let scope_chain = csharp_scope_chain(&def_node, source);
4004 symbols.push(Symbol {
4005 name: node_text(source, &name_node).to_string(),
4006 kind: SymbolKind::Struct,
4007 range: node_range_with_decorators(&def_node, source, lang),
4008 signature: Some(extract_signature(source, &def_node)),
4009 scope_chain: scope_chain.clone(),
4010 exported: false,
4011 parent: scope_chain.last().cloned(),
4012 });
4013 }
4014
4015 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
4016 let scope_chain = csharp_scope_chain(&def_node, source);
4017 symbols.push(Symbol {
4018 name: node_text(source, &name_node).to_string(),
4019 kind: SymbolKind::Enum,
4020 range: node_range_with_decorators(&def_node, source, lang),
4021 signature: Some(extract_signature(source, &def_node)),
4022 scope_chain: scope_chain.clone(),
4023 exported: false,
4024 parent: scope_chain.last().cloned(),
4025 });
4026 }
4027
4028 if let (Some(name_node), Some(def_node)) = (method_name_node, method_def_node) {
4029 let scope_chain = csharp_scope_chain(&def_node, source);
4030 symbols.push(Symbol {
4031 name: node_text(source, &name_node).to_string(),
4032 kind: SymbolKind::Method,
4033 range: node_range_with_decorators(&def_node, source, lang),
4034 signature: Some(extract_signature(source, &def_node)),
4035 scope_chain: scope_chain.clone(),
4036 exported: false,
4037 parent: scope_chain.last().cloned(),
4038 });
4039 }
4040
4041 if let (Some(name_node), Some(def_node)) = (property_name_node, property_def_node) {
4042 let scope_chain = csharp_scope_chain(&def_node, source);
4043 symbols.push(Symbol {
4044 name: node_text(source, &name_node).to_string(),
4045 kind: SymbolKind::Variable,
4046 range: node_range_with_decorators(&def_node, source, lang),
4047 signature: Some(extract_signature(source, &def_node)),
4048 scope_chain: scope_chain.clone(),
4049 exported: false,
4050 parent: scope_chain.last().cloned(),
4051 });
4052 }
4053
4054 if let (Some(name_node), Some(def_node)) = (namespace_name_node, namespace_def_node) {
4055 let scope_chain = csharp_scope_chain(&def_node, source);
4056 symbols.push(Symbol {
4057 name: node_text(source, &name_node).to_string(),
4058 kind: SymbolKind::TypeAlias,
4059 range: node_range_with_decorators(&def_node, source, lang),
4060 signature: Some(extract_signature(source, &def_node)),
4061 scope_chain: scope_chain.clone(),
4062 exported: false,
4063 parent: scope_chain.last().cloned(),
4064 });
4065 }
4066 }
4067
4068 dedup_symbols(&mut symbols);
4069 Ok(symbols)
4070}
4071
4072fn find_type_identifier_recursive(node: &Node, source: &str) -> Option<String> {
4074 if node.kind() == "type_identifier" {
4075 return Some(node_text(source, node).to_string());
4076 }
4077 let mut cursor = node.walk();
4078 if cursor.goto_first_child() {
4079 loop {
4080 if let Some(result) = find_type_identifier_recursive(&cursor.node(), source) {
4081 return Some(result);
4082 }
4083 if !cursor.goto_next_sibling() {
4084 break;
4085 }
4086 }
4087 }
4088 None
4089}
4090
4091fn extract_bash_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
4095 let lang = LangId::Bash;
4096 let capture_names = query.capture_names();
4097
4098 let mut symbols = Vec::new();
4099 let mut cursor = QueryCursor::new();
4100 let mut matches = cursor.matches(query, *root, source.as_bytes());
4101
4102 while let Some(m) = {
4103 matches.advance();
4104 matches.get()
4105 } {
4106 let mut fn_name_node = None;
4107 let mut fn_def_node = None;
4108
4109 for cap in m.captures {
4110 let Some(&name) = capture_names.get(cap.index as usize) else {
4111 continue;
4112 };
4113 match name {
4114 "fn.name" => fn_name_node = Some(cap.node),
4115 "fn.def" => fn_def_node = Some(cap.node),
4116 _ => {}
4117 }
4118 }
4119
4120 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
4121 symbols.push(Symbol {
4122 name: node_text(source, &name_node).to_string(),
4123 kind: SymbolKind::Function,
4124 range: node_range_with_decorators(&def_node, source, lang),
4125 signature: Some(extract_signature(source, &def_node)),
4126 scope_chain: vec![],
4127 exported: false,
4128 parent: None,
4129 });
4130 }
4131 }
4132
4133 Ok(symbols)
4134}
4135
4136fn extract_pascal_symbols(
4137 source: &str,
4138 root: &Node,
4139 query: &Query,
4140) -> Result<Vec<Symbol>, AftError> {
4141 let lang = LangId::Pascal;
4142 let capture_names = query.capture_names();
4143
4144 let mut symbols = Vec::new();
4145 let mut cursor = QueryCursor::new();
4146 let mut matches = cursor.matches(query, *root, source.as_bytes());
4147
4148 while let Some(m) = {
4149 matches.advance();
4150 matches.get()
4151 } {
4152 let mut program_name_node = None;
4153 let mut program_def_node = None;
4154 let mut unit_name_node = None;
4155 let mut unit_def_node = None;
4156 let mut type_name_node = None;
4157 let mut type_def_node = None;
4158 let mut const_name_node = None;
4159 let mut const_def_node = None;
4160 let mut var_name_node = None;
4161 let mut var_def_node = None;
4162 let mut proc_name_node = None;
4163 let mut proc_def_node = None;
4164
4165 for cap in m.captures {
4166 let Some(&name) = capture_names.get(cap.index as usize) else {
4167 continue;
4168 };
4169 match name {
4170 "program.name" => program_name_node = Some(cap.node),
4171 "program.def" => program_def_node = Some(cap.node),
4172 "unit.name" => unit_name_node = Some(cap.node),
4173 "unit.def" => unit_def_node = Some(cap.node),
4174 "type.name" => type_name_node = Some(cap.node),
4175 "type.def" => type_def_node = Some(cap.node),
4176 "const.name" => const_name_node = Some(cap.node),
4177 "const.def" => const_def_node = Some(cap.node),
4178 "var.name" => var_name_node = Some(cap.node),
4179 "var.def" => var_def_node = Some(cap.node),
4180 "proc.name" => proc_name_node = Some(cap.node),
4181 "proc.def" => proc_def_node = Some(cap.node),
4182 _ => {}
4183 }
4184 }
4185
4186 if let (Some(name_node), Some(def_node)) = (program_name_node, program_def_node) {
4187 symbols.push(Symbol {
4188 name: node_text(source, &name_node).to_string(),
4189 kind: SymbolKind::Class,
4190 range: node_range_with_decorators(&def_node, source, lang),
4191 signature: Some(extract_signature(source, &def_node)),
4192 scope_chain: vec![],
4193 exported: true,
4194 parent: None,
4195 });
4196 }
4197
4198 if let (Some(name_node), Some(def_node)) = (unit_name_node, unit_def_node) {
4199 symbols.push(Symbol {
4200 name: node_text(source, &name_node).to_string(),
4201 kind: SymbolKind::Class,
4202 range: node_range_with_decorators(&def_node, source, lang),
4203 signature: Some(extract_signature(source, &def_node)),
4204 scope_chain: vec![],
4205 exported: true,
4206 parent: None,
4207 });
4208 }
4209
4210 if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
4211 let kind = pascal_type_kind(&def_node, source);
4212 symbols.push(Symbol {
4213 name: node_text(source, &name_node).to_string(),
4214 kind,
4215 range: node_range_with_decorators(&def_node, source, lang),
4216 signature: Some(extract_signature(source, &def_node)),
4217 scope_chain: vec![],
4218 exported: true,
4219 parent: None,
4220 });
4221 }
4222
4223 if let (Some(name_node), Some(def_node)) = (const_name_node, const_def_node) {
4224 symbols.push(Symbol {
4225 name: node_text(source, &name_node).to_string(),
4226 kind: SymbolKind::Variable,
4227 range: node_range_with_decorators(&def_node, source, lang),
4228 signature: Some(extract_signature(source, &def_node)),
4229 scope_chain: vec![],
4230 exported: true,
4231 parent: None,
4232 });
4233 }
4234
4235 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
4236 symbols.push(Symbol {
4237 name: node_text(source, &name_node).to_string(),
4238 kind: SymbolKind::Variable,
4239 range: node_range_with_decorators(&def_node, source, lang),
4240 signature: Some(extract_signature(source, &def_node)),
4241 scope_chain: vec![],
4242 exported: true,
4243 parent: None,
4244 });
4245 }
4246
4247 if let (Some(name_node), Some(def_node)) = (proc_name_node, proc_def_node) {
4248 if def_node.kind() == "declProc" {
4249 if let Some(parent) = def_node.parent() {
4250 if parent.kind() == "defProc" {
4251 continue;
4252 }
4253 }
4254 }
4255
4256 let (name, scope_chain) = extract_pascal_name_and_scope(&name_node, &def_node, source);
4257 let kind = if scope_chain.is_empty() {
4258 SymbolKind::Function
4259 } else {
4260 SymbolKind::Method
4261 };
4262
4263 symbols.push(Symbol {
4264 name,
4265 kind,
4266 range: node_range_with_decorators(&def_node, source, lang),
4267 signature: Some(extract_signature(source, &def_node)),
4268 parent: scope_chain.last().cloned(),
4269 scope_chain,
4270 exported: true,
4271 });
4272 }
4273 }
4274
4275 dedup_symbols(&mut symbols);
4276 Ok(symbols)
4277}
4278
4279fn pascal_type_kind(node: &Node, _source: &str) -> SymbolKind {
4280 let mut cursor = node.walk();
4281 for child in node.children(&mut cursor) {
4282 if child.kind() == "declClass" {
4283 let mut child_cursor = child.walk();
4284 for grandchild in child.children(&mut child_cursor) {
4285 if grandchild.kind() == "kRecord" {
4286 return SymbolKind::Struct;
4287 }
4288 }
4289 return SymbolKind::Class;
4290 }
4291 if child.kind() == "declIntf" {
4292 return SymbolKind::Interface;
4293 }
4294 if child.kind() == "type" {
4295 let mut child_cursor = child.walk();
4296 for grandchild in child.children(&mut child_cursor) {
4297 if grandchild.kind() == "declEnum" {
4298 return SymbolKind::Enum;
4299 }
4300 }
4301 }
4302 }
4303 SymbolKind::TypeAlias
4304}
4305
4306fn extract_pascal_name_and_scope(
4307 name_node: &Node,
4308 def_node: &Node,
4309 source: &str,
4310) -> (String, Vec<String>) {
4311 let mut scope_chain = Vec::new();
4312 let mut name = node_text(source, name_node).to_string();
4313
4314 if name_node.kind() == "genericDot" {
4315 let mut cursor = name_node.walk();
4316 let idents: Vec<Node> = name_node
4317 .children(&mut cursor)
4318 .filter(|c| c.kind() == "identifier")
4319 .collect();
4320 if idents.len() >= 2 {
4321 let class_name = node_text(source, &idents[0]).to_string();
4322 scope_chain.push(class_name);
4323 name = node_text(source, &idents[1]).to_string();
4324 }
4325 }
4326
4327 let mut current = def_node.parent();
4328 while let Some(parent) = current {
4329 if parent.kind() == "declType" {
4330 if let Some(type_name_node) = parent.child_by_field_name("name").or_else(|| {
4331 let mut cursor = parent.walk();
4332 let mut found = None;
4333 for child in parent.children(&mut cursor) {
4334 if child.kind() == "identifier" {
4335 found = Some(child);
4336 break;
4337 }
4338 }
4339 found
4340 }) {
4341 scope_chain.insert(0, node_text(source, &type_name_node).to_string());
4342 }
4343 }
4344 current = parent.parent();
4345 }
4346
4347 (name, scope_chain)
4348}
4349
4350fn solidity_scope_chain(node: &Node, source: &str) -> Vec<String> {
4353 let mut chain = Vec::new();
4354 let mut current = node.parent();
4355
4356 while let Some(parent) = current {
4357 match parent.kind() {
4358 "contract_declaration" | "library_declaration" | "interface_declaration" => {
4359 if let Some(name_node) = parent.child_by_field_name("name") {
4360 chain.push(node_text(source, &name_node).to_string());
4361 }
4362 }
4363 _ => {}
4364 }
4365 current = parent.parent();
4366 }
4367
4368 chain.reverse();
4369 chain
4370}
4371
4372fn extract_solidity_symbols(
4373 source: &str,
4374 root: &Node,
4375 query: &Query,
4376) -> Result<Vec<Symbol>, AftError> {
4377 let lang = LangId::Solidity;
4378 let capture_names = query.capture_names();
4379
4380 let mut symbols = Vec::new();
4381 let mut cursor = QueryCursor::new();
4382 let mut matches = cursor.matches(query, *root, source.as_bytes());
4383
4384 while let Some(m) = {
4385 matches.advance();
4386 matches.get()
4387 } {
4388 let mut contract_name_node = None;
4389 let mut contract_def_node = None;
4390 let mut library_name_node = None;
4391 let mut library_def_node = None;
4392 let mut interface_name_node = None;
4393 let mut interface_def_node = None;
4394 let mut fn_name_node = None;
4395 let mut fn_def_node = None;
4396 let mut modifier_name_node = None;
4397 let mut modifier_def_node = None;
4398 let mut constructor_def_node = None;
4399 let mut fallback_receive_def_node = None;
4400 let mut event_name_node = None;
4401 let mut event_def_node = None;
4402 let mut error_name_node = None;
4403 let mut error_def_node = None;
4404 let mut struct_name_node = None;
4405 let mut struct_def_node = None;
4406 let mut enum_name_node = None;
4407 let mut enum_def_node = None;
4408 let mut var_name_node = None;
4409 let mut var_def_node = None;
4410
4411 for cap in m.captures {
4412 let Some(&name) = capture_names.get(cap.index as usize) else {
4413 continue;
4414 };
4415 match name {
4416 "contract.name" => contract_name_node = Some(cap.node),
4417 "contract.def" => contract_def_node = Some(cap.node),
4418 "library.name" => library_name_node = Some(cap.node),
4419 "library.def" => library_def_node = Some(cap.node),
4420 "interface.name" => interface_name_node = Some(cap.node),
4421 "interface.def" => interface_def_node = Some(cap.node),
4422 "fn.name" => fn_name_node = Some(cap.node),
4423 "fn.def" => fn_def_node = Some(cap.node),
4424 "modifier.name" => modifier_name_node = Some(cap.node),
4425 "modifier.def" => modifier_def_node = Some(cap.node),
4426 "constructor.def" => constructor_def_node = Some(cap.node),
4427 "fallback_receive.def" => fallback_receive_def_node = Some(cap.node),
4428 "event.name" => event_name_node = Some(cap.node),
4429 "event.def" => event_def_node = Some(cap.node),
4430 "error.name" => error_name_node = Some(cap.node),
4431 "error.def" => error_def_node = Some(cap.node),
4432 "struct.name" => struct_name_node = Some(cap.node),
4433 "struct.def" => struct_def_node = Some(cap.node),
4434 "enum.name" => enum_name_node = Some(cap.node),
4435 "enum.def" => enum_def_node = Some(cap.node),
4436 "var.name" => var_name_node = Some(cap.node),
4437 "var.def" => var_def_node = Some(cap.node),
4438 _ => {}
4439 }
4440 }
4441
4442 if let (Some(name_node), Some(def_node)) = (contract_name_node, contract_def_node) {
4444 symbols.push(Symbol {
4445 name: node_text(source, &name_node).to_string(),
4446 kind: SymbolKind::Class,
4447 range: node_range_with_decorators(&def_node, source, lang),
4448 signature: Some(extract_signature(source, &def_node)),
4449 scope_chain: vec![],
4450 exported: true,
4451 parent: None,
4452 });
4453 }
4454
4455 if let (Some(name_node), Some(def_node)) = (library_name_node, library_def_node) {
4457 symbols.push(Symbol {
4458 name: node_text(source, &name_node).to_string(),
4459 kind: SymbolKind::Class,
4460 range: node_range_with_decorators(&def_node, source, lang),
4461 signature: Some(extract_signature(source, &def_node)),
4462 scope_chain: vec![],
4463 exported: true,
4464 parent: None,
4465 });
4466 }
4467
4468 if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
4470 symbols.push(Symbol {
4471 name: node_text(source, &name_node).to_string(),
4472 kind: SymbolKind::Interface,
4473 range: node_range_with_decorators(&def_node, source, lang),
4474 signature: Some(extract_signature(source, &def_node)),
4475 scope_chain: vec![],
4476 exported: true,
4477 parent: None,
4478 });
4479 }
4480
4481 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
4483 let scope_chain = solidity_scope_chain(&def_node, source);
4484 let kind = if scope_chain.is_empty() {
4485 SymbolKind::Function
4486 } else {
4487 SymbolKind::Method
4488 };
4489 symbols.push(Symbol {
4490 name: node_text(source, &name_node).to_string(),
4491 kind,
4492 range: node_range_with_decorators(&def_node, source, lang),
4493 signature: Some(extract_signature(source, &def_node)),
4494 parent: scope_chain.last().cloned(),
4495 scope_chain,
4496 exported: true,
4497 });
4498 }
4499
4500 if let (Some(name_node), Some(def_node)) = (modifier_name_node, modifier_def_node) {
4502 let scope_chain = solidity_scope_chain(&def_node, source);
4503 symbols.push(Symbol {
4504 name: node_text(source, &name_node).to_string(),
4505 kind: SymbolKind::Method,
4506 range: node_range_with_decorators(&def_node, source, lang),
4507 signature: Some(extract_signature(source, &def_node)),
4508 parent: scope_chain.last().cloned(),
4509 scope_chain,
4510 exported: true,
4511 });
4512 }
4513
4514 if let Some(def_node) = constructor_def_node {
4516 let scope_chain = solidity_scope_chain(&def_node, source);
4517 symbols.push(Symbol {
4518 name: "constructor".to_string(),
4519 kind: SymbolKind::Method,
4520 range: node_range_with_decorators(&def_node, source, lang),
4521 signature: Some(extract_signature(source, &def_node)),
4522 parent: scope_chain.last().cloned(),
4523 scope_chain,
4524 exported: true,
4525 });
4526 }
4527
4528 if let Some(def_node) = fallback_receive_def_node {
4530 let scope_chain = solidity_scope_chain(&def_node, source);
4531 let signature = extract_signature(source, &def_node);
4532 let name = if signature.trim_start().starts_with("receive") {
4533 "receive"
4534 } else {
4535 "fallback"
4536 };
4537 symbols.push(Symbol {
4538 name: name.to_string(),
4539 kind: SymbolKind::Method,
4540 range: node_range_with_decorators(&def_node, source, lang),
4541 signature: Some(signature),
4542 parent: scope_chain.last().cloned(),
4543 scope_chain,
4544 exported: true,
4545 });
4546 }
4547
4548 if let (Some(name_node), Some(def_node)) = (event_name_node, event_def_node) {
4550 let scope_chain = solidity_scope_chain(&def_node, source);
4551 symbols.push(Symbol {
4552 name: node_text(source, &name_node).to_string(),
4553 kind: SymbolKind::Function,
4554 range: node_range_with_decorators(&def_node, source, lang),
4555 signature: Some(extract_signature(source, &def_node)),
4556 parent: scope_chain.last().cloned(),
4557 scope_chain,
4558 exported: true,
4559 });
4560 }
4561
4562 if let (Some(name_node), Some(def_node)) = (error_name_node, error_def_node) {
4564 let scope_chain = solidity_scope_chain(&def_node, source);
4565 symbols.push(Symbol {
4566 name: node_text(source, &name_node).to_string(),
4567 kind: SymbolKind::TypeAlias,
4568 range: node_range_with_decorators(&def_node, source, lang),
4569 signature: Some(extract_signature(source, &def_node)),
4570 parent: scope_chain.last().cloned(),
4571 scope_chain,
4572 exported: true,
4573 });
4574 }
4575
4576 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
4578 let scope_chain = solidity_scope_chain(&def_node, source);
4579 symbols.push(Symbol {
4580 name: node_text(source, &name_node).to_string(),
4581 kind: SymbolKind::Struct,
4582 range: node_range_with_decorators(&def_node, source, lang),
4583 signature: Some(extract_signature(source, &def_node)),
4584 parent: scope_chain.last().cloned(),
4585 scope_chain,
4586 exported: true,
4587 });
4588 }
4589
4590 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
4592 let scope_chain = solidity_scope_chain(&def_node, source);
4593 symbols.push(Symbol {
4594 name: node_text(source, &name_node).to_string(),
4595 kind: SymbolKind::Enum,
4596 range: node_range_with_decorators(&def_node, source, lang),
4597 signature: Some(extract_signature(source, &def_node)),
4598 parent: scope_chain.last().cloned(),
4599 scope_chain,
4600 exported: true,
4601 });
4602 }
4603
4604 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
4606 let scope_chain = solidity_scope_chain(&def_node, source);
4607 symbols.push(Symbol {
4608 name: node_text(source, &name_node).to_string(),
4609 kind: SymbolKind::Variable,
4610 range: node_range_with_decorators(&def_node, source, lang),
4611 signature: Some(extract_signature(source, &def_node)),
4612 parent: scope_chain.last().cloned(),
4613 scope_chain,
4614 exported: true,
4615 });
4616 }
4617 }
4618
4619 dedup_symbols(&mut symbols);
4620 Ok(symbols)
4621}
4622
4623fn extract_r_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
4624 let lang = LangId::R;
4625 let capture_names = query.capture_names();
4626
4627 let mut symbols = Vec::new();
4628 let mut cursor = QueryCursor::new();
4629 let mut matches = cursor.matches(query, *root, source.as_bytes());
4630
4631 while let Some(m) = {
4632 matches.advance();
4633 matches.get()
4634 } {
4635 let mut assign_node = None;
4636 let mut function_node = None;
4637
4638 for cap in m.captures {
4639 let Some(&name) = capture_names.get(cap.index as usize) else {
4640 continue;
4641 };
4642 match name {
4643 "assign.def" => assign_node = Some(cap.node),
4644 "function.def" => function_node = Some(cap.node),
4645 _ => {}
4646 }
4647 }
4648
4649 if let Some(def_node) = function_node {
4650 if let Some(symbol) = r_rightward_function_assignment_symbol(source, &def_node, lang) {
4651 symbols.push(symbol);
4652 }
4653 continue;
4654 }
4655
4656 let Some(def_node) = assign_node else {
4657 continue;
4658 };
4659 let Some(assignment) = r_assignment_parts(source, &def_node) else {
4660 continue;
4661 };
4662 let Some(name_node) = r_assignment_name_node(&assignment) else {
4663 continue;
4664 };
4665 if name_node.kind() != "identifier" {
4666 continue;
4667 }
4668
4669 let value_is_function = r_node_contains_kind(&assignment.value, "function_definition");
4670 if !value_is_function && !r_is_top_level_assignment(&def_node) {
4671 continue;
4672 }
4673
4674 symbols.push(Symbol {
4675 name: node_text(source, &name_node).to_string(),
4676 kind: if value_is_function {
4677 SymbolKind::Function
4678 } else {
4679 SymbolKind::Variable
4680 },
4681 range: node_range_with_decorators(&def_node, source, lang),
4682 signature: Some(extract_signature(source, &def_node)),
4683 scope_chain: vec![],
4684 exported: true,
4685 parent: None,
4686 });
4687 }
4688
4689 dedup_symbols(&mut symbols);
4690 Ok(symbols)
4691}
4692
4693fn extract_groovy_symbols(
4694 source: &str,
4695 root: &Node,
4696 query: &Query,
4697) -> Result<Vec<Symbol>, AftError> {
4698 let lang = LangId::Groovy;
4699 let capture_names = query.capture_names();
4700 let mut symbols = Vec::new();
4701 let mut cursor = QueryCursor::new();
4702 let mut matches = cursor.matches(query, *root, source.as_bytes());
4703
4704 while let Some(m) = {
4705 matches.advance();
4706 matches.get()
4707 } {
4708 let mut class_name_node = None;
4709 let mut class_def_node = None;
4710 let mut interface_name_node = None;
4711 let mut interface_def_node = None;
4712 let mut trait_name_node = None;
4713 let mut trait_def_node = None;
4714 let mut enum_name_node = None;
4715 let mut enum_def_node = None;
4716 let mut fn_name_node = None;
4717 let mut fn_def_node = None;
4718 let mut var_name_node = None;
4719 let mut var_def_node = None;
4720 let mut pipeline_def_node = None;
4721
4722 for cap in m.captures {
4723 let Some(&name) = capture_names.get(cap.index as usize) else {
4724 continue;
4725 };
4726 match name {
4727 "class.name" => class_name_node = Some(cap.node),
4728 "class.def" => class_def_node = Some(cap.node),
4729 "interface.name" => interface_name_node = Some(cap.node),
4730 "interface.def" => interface_def_node = Some(cap.node),
4731 "trait.name" => trait_name_node = Some(cap.node),
4732 "trait.def" => trait_def_node = Some(cap.node),
4733 "enum.name" => enum_name_node = Some(cap.node),
4734 "enum.def" => enum_def_node = Some(cap.node),
4735 "fn.name" => fn_name_node = Some(cap.node),
4736 "fn.def" => fn_def_node = Some(cap.node),
4737 "var.name" => var_name_node = Some(cap.node),
4738 "var.def" => var_def_node = Some(cap.node),
4739 "pipeline.def" => pipeline_def_node = Some(cap.node),
4740 _ => {}
4741 }
4742 }
4743
4744 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
4745 push_captured_symbol(
4746 &mut symbols,
4747 source,
4748 lang,
4749 name_node,
4750 def_node,
4751 SymbolKind::Class,
4752 groovy_scope_chain(&def_node, source),
4753 true,
4754 );
4755 }
4756 if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
4757 push_captured_symbol(
4758 &mut symbols,
4759 source,
4760 lang,
4761 name_node,
4762 def_node,
4763 SymbolKind::Interface,
4764 groovy_scope_chain(&def_node, source),
4765 true,
4766 );
4767 }
4768 if let (Some(name_node), Some(def_node)) = (trait_name_node, trait_def_node) {
4769 push_captured_symbol(
4770 &mut symbols,
4771 source,
4772 lang,
4773 name_node,
4774 def_node,
4775 SymbolKind::Interface,
4776 groovy_scope_chain(&def_node, source),
4777 true,
4778 );
4779 }
4780 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
4781 push_captured_symbol(
4782 &mut symbols,
4783 source,
4784 lang,
4785 name_node,
4786 def_node,
4787 SymbolKind::Enum,
4788 groovy_scope_chain(&def_node, source),
4789 true,
4790 );
4791 }
4792 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
4793 let scope_chain = groovy_scope_chain(&def_node, source);
4794 let kind = if scope_chain.is_empty() {
4795 SymbolKind::Function
4796 } else {
4797 SymbolKind::Method
4798 };
4799 push_captured_symbol(
4800 &mut symbols,
4801 source,
4802 lang,
4803 name_node,
4804 def_node,
4805 kind,
4806 scope_chain,
4807 true,
4808 );
4809 }
4810 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
4811 push_captured_symbol(
4812 &mut symbols,
4813 source,
4814 lang,
4815 name_node,
4816 def_node,
4817 SymbolKind::Variable,
4818 groovy_scope_chain(&def_node, source),
4819 true,
4820 );
4821 }
4822 if let Some(def_node) = pipeline_def_node {
4823 symbols.push(Symbol {
4824 name: "pipeline".to_string(),
4825 kind: SymbolKind::Function,
4826 range: node_range_with_decorators(&def_node, source, lang),
4827 signature: Some(extract_signature(source, &def_node)),
4828 scope_chain: vec![],
4829 exported: true,
4830 parent: None,
4831 });
4832 }
4833 }
4834
4835 collect_groovy_task_symbols(source, root, &mut symbols);
4836 dedup_symbols(&mut symbols);
4837 Ok(symbols)
4838}
4839
4840fn r_rightward_function_assignment_symbol(
4841 source: &str,
4842 function_node: &Node,
4843 lang: LangId,
4844) -> Option<Symbol> {
4845 let body = function_node.child_by_field_name("body")?;
4846 let assignment = r_assignment_parts(source, &body)?;
4847 if !matches!(assignment.operator.as_str(), "->" | "->>") {
4848 return None;
4849 }
4850 let function_text = node_text(source, function_node);
4851 let name = r_rightward_assignment_name_from_text(function_text).or_else(|| {
4852 let name_node = assignment.right;
4853 (name_node.kind() == "identifier").then(|| node_text(source, &name_node).to_string())
4854 })?;
4855
4856 let signature = r_rightward_function_signature(source, function_node, &name);
4857
4858 Some(Symbol {
4859 name,
4860 kind: SymbolKind::Function,
4861 range: node_range_with_decorators(function_node, source, lang),
4862 signature: Some(signature),
4863 scope_chain: vec![],
4864 exported: true,
4865 parent: None,
4866 })
4867}
4868
4869fn r_rightward_function_signature(source: &str, function_node: &Node, name: &str) -> String {
4870 let signature = extract_signature(source, function_node);
4871 if signature.contains(name) {
4872 signature
4873 } else {
4874 format!("{signature} -> {name}")
4875 }
4876}
4877
4878fn r_rightward_assignment_name_from_text(text: &str) -> Option<String> {
4879 let (_, after) = text.rsplit_once("->>").or_else(|| text.rsplit_once("->"))?;
4880 let trimmed = after.trim_start();
4881 let name: String = trimmed
4882 .chars()
4883 .take_while(|ch| ch.is_alphanumeric() || *ch == '_' || *ch == '.')
4884 .collect();
4885 (!name.is_empty()).then_some(name)
4886}
4887
4888struct RAssignmentParts<'tree> {
4889 operator: String,
4890 left: Node<'tree>,
4891 right: Node<'tree>,
4892 value: Node<'tree>,
4893}
4894
4895fn r_assignment_name_node<'tree>(assignment: &RAssignmentParts<'tree>) -> Option<Node<'tree>> {
4896 match assignment.operator.as_str() {
4897 "<-" | "=" | "<<-" => Some(assignment.left),
4898 "->" | "->>" => Some(assignment.right),
4899 _ => None,
4900 }
4901}
4902
4903fn r_assignment_parts<'tree>(source: &str, node: &Node<'tree>) -> Option<RAssignmentParts<'tree>> {
4904 if node.kind() != "binary_operator" {
4905 return None;
4906 }
4907
4908 let mut operator = None;
4909 let mut operator_index = None;
4910 for index in 0..node.child_count() {
4911 let child = node.child(index as u32)?;
4912 let text = node_text(source, &child).trim();
4913 if matches!(text, "<-" | "=" | "<<-" | "->" | "->>") {
4914 operator = Some(text.to_string());
4915 operator_index = Some(index);
4916 break;
4917 }
4918 }
4919
4920 let operator = operator?;
4921 let operator_index = operator_index?;
4922
4923 let left = r_nearest_named_child_before(node, operator_index)
4924 .or_else(|| node.child_by_field_name("lhs"))
4925 .or_else(|| node.child_by_field_name("left"))?;
4926 let right = r_nearest_named_child_after(node, operator_index)
4927 .or_else(|| node.child_by_field_name("rhs"))
4928 .or_else(|| node.child_by_field_name("right"))?;
4929
4930 let value = match operator.as_str() {
4931 "<-" | "=" | "<<-" => right,
4932 "->" | "->>" => left,
4933 _ => return None,
4934 };
4935
4936 Some(RAssignmentParts {
4937 operator,
4938 left,
4939 right,
4940 value,
4941 })
4942}
4943
4944fn r_nearest_named_child_before<'tree>(node: &Node<'tree>, index: usize) -> Option<Node<'tree>> {
4945 (0..index)
4946 .rev()
4947 .filter_map(|child_index| node.child(child_index as u32))
4948 .find(|child| child.is_named())
4949}
4950
4951fn r_nearest_named_child_after<'tree>(node: &Node<'tree>, index: usize) -> Option<Node<'tree>> {
4952 ((index + 1)..node.child_count())
4953 .filter_map(|child_index| node.child(child_index as u32))
4954 .find(|child| child.is_named())
4955}
4956
4957fn r_node_contains_kind(node: &Node, kind: &str) -> bool {
4958 if node.kind() == kind {
4959 return true;
4960 }
4961
4962 let mut cursor = node.walk();
4963 let found = node
4964 .children(&mut cursor)
4965 .any(|child| r_node_contains_kind(&child, kind));
4966 found
4967}
4968
4969fn r_is_top_level_assignment(node: &Node) -> bool {
4970 let mut current = node.parent();
4971 while let Some(parent) = current {
4972 match parent.kind() {
4973 "program" => return true,
4974 "braced_expression" | "function_definition" => return false,
4975 _ => current = parent.parent(),
4976 }
4977 }
4978 false
4979}
4980
4981fn extract_objc_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
4982 let lang = LangId::ObjC;
4983 let capture_names = query.capture_names();
4984
4985 let mut symbols = Vec::new();
4986 let mut cursor = QueryCursor::new();
4987 let mut matches = cursor.matches(query, *root, source.as_bytes());
4988
4989 while let Some(m) = {
4990 matches.advance();
4991 matches.get()
4992 } {
4993 for cap in m.captures {
4994 let Some(&name) = capture_names.get(cap.index as usize) else {
4995 continue;
4996 };
4997
4998 let def_node = cap.node;
4999 match name {
5000 "class.def" => {
5001 if let Some(symbol_name) = objc_container_name(source, &def_node) {
5002 let scope_chain = objc_scope_chain(&def_node, source);
5003 symbols.push(Symbol {
5004 name: symbol_name,
5005 kind: SymbolKind::Class,
5006 range: node_range_with_decorators(&def_node, source, lang),
5007 signature: Some(extract_signature(source, &def_node)),
5008 parent: scope_chain.last().cloned(),
5009 scope_chain,
5010 exported: false,
5011 });
5012 }
5013 }
5014 "interface.def" => {
5015 if let Some(symbol_name) = objc_container_name(source, &def_node) {
5016 let scope_chain = objc_scope_chain(&def_node, source);
5017 symbols.push(Symbol {
5018 name: symbol_name,
5019 kind: SymbolKind::Interface,
5020 range: node_range_with_decorators(&def_node, source, lang),
5021 signature: Some(extract_signature(source, &def_node)),
5022 parent: scope_chain.last().cloned(),
5023 scope_chain,
5024 exported: false,
5025 });
5026 }
5027 }
5028 "method.def" => {
5029 if let Some(symbol_name) = objc_method_selector(source, &def_node) {
5030 let scope_chain = objc_scope_chain(&def_node, source);
5031 symbols.push(Symbol {
5032 name: symbol_name,
5033 kind: SymbolKind::Method,
5034 range: node_range_with_decorators(&def_node, source, lang),
5035 signature: Some(extract_signature(source, &def_node)),
5036 parent: scope_chain.last().cloned(),
5037 scope_chain,
5038 exported: false,
5039 });
5040 }
5041 }
5042 "property.def" => {
5043 if let Some(symbol_name) = objc_property_name(source, &def_node) {
5044 let scope_chain = objc_scope_chain(&def_node, source);
5045 symbols.push(Symbol {
5046 name: symbol_name,
5047 kind: SymbolKind::Variable,
5048 range: node_range_with_decorators(&def_node, source, lang),
5049 signature: Some(extract_signature(source, &def_node)),
5050 parent: scope_chain.last().cloned(),
5051 scope_chain,
5052 exported: false,
5053 });
5054 }
5055 }
5056 "fn.def" => {
5057 if let Some(symbol_name) = objc_function_name(source, &def_node) {
5058 let scope_chain = objc_scope_chain(&def_node, source);
5059 symbols.push(Symbol {
5060 name: symbol_name,
5061 kind: SymbolKind::Function,
5062 range: node_range_with_decorators(&def_node, source, lang),
5063 signature: Some(extract_signature(source, &def_node)),
5064 parent: scope_chain.last().cloned(),
5065 scope_chain,
5066 exported: false,
5067 });
5068 }
5069 }
5070 "type.def" => {
5071 if let Some(symbol_name) = objc_typedef_name(source, &def_node) {
5072 let scope_chain = objc_scope_chain(&def_node, source);
5073 symbols.push(Symbol {
5074 name: symbol_name,
5075 kind: SymbolKind::TypeAlias,
5076 range: node_range_with_decorators(&def_node, source, lang),
5077 signature: Some(extract_signature(source, &def_node)),
5078 parent: scope_chain.last().cloned(),
5079 scope_chain,
5080 exported: false,
5081 });
5082 }
5083 }
5084 _ => {}
5085 }
5086 }
5087 }
5088
5089 dedup_symbols(&mut symbols);
5090 Ok(symbols)
5091}
5092
5093fn objc_container_name(source: &str, node: &Node) -> Option<String> {
5094 objc_first_direct_identifier(source, node)
5095}
5096
5097fn objc_method_selector(source: &str, node: &Node) -> Option<String> {
5098 let mut bare_selector = None;
5099 let mut keyword_segments = Vec::new();
5100 let mut cursor = node.walk();
5101
5102 let direct_named_children = node.named_children(&mut cursor).collect::<Vec<_>>();
5103 for (index, child) in direct_named_children.iter().enumerate() {
5104 match child.kind() {
5105 "identifier" => {
5106 let next_kind = direct_named_children.get(index + 1).map(|next| next.kind());
5107 if next_kind == Some("method_parameter") {
5108 keyword_segments.push(format!(
5109 "{}:",
5110 node_text(source, child).trim_end_matches(':')
5111 ));
5112 } else {
5113 bare_selector.get_or_insert_with(|| node_text(source, child).to_string());
5114 }
5115 }
5116 "keyword_declarator" => {
5117 if let Some(segment) = objc_first_direct_identifier(source, child) {
5118 keyword_segments.push(format!("{}:", segment.trim_end_matches(':')));
5119 }
5120 }
5121 _ => {}
5122 }
5123 }
5124
5125 if keyword_segments.is_empty() {
5126 bare_selector
5127 } else {
5128 Some(keyword_segments.join(""))
5129 }
5130}
5131
5132fn objc_property_name(source: &str, node: &Node) -> Option<String> {
5133 find_child_by_kind(*node, "atomic_declaration")
5134 .as_ref()
5135 .and_then(|declaration| objc_declarator_name(source, declaration))
5136 .or_else(|| objc_declarator_name(source, node))
5137}
5138
5139fn objc_function_name(source: &str, node: &Node) -> Option<String> {
5140 node.child_by_field_name("declarator")
5141 .as_ref()
5142 .and_then(|declarator| objc_declarator_name(source, declarator))
5143}
5144
5145fn objc_typedef_name(source: &str, node: &Node) -> Option<String> {
5146 node.child_by_field_name("declarator")
5147 .as_ref()
5148 .and_then(|declarator| objc_declarator_name(source, declarator))
5149}
5150
5151fn objc_scope_chain(node: &Node, source: &str) -> Vec<String> {
5152 let mut scopes = Vec::new();
5153 let mut current = node.parent();
5154 while let Some(parent) = current {
5155 if matches!(
5156 parent.kind(),
5157 "class_interface" | "class_implementation" | "protocol_declaration"
5158 ) {
5159 if let Some(name) = objc_container_name(source, &parent) {
5160 scopes.push(name);
5161 }
5162 }
5163 current = parent.parent();
5164 }
5165 scopes.reverse();
5166 scopes
5167}
5168
5169fn objc_first_direct_identifier(source: &str, node: &Node) -> Option<String> {
5170 let mut cursor = node.walk();
5171 if !cursor.goto_first_child() {
5172 return None;
5173 }
5174
5175 loop {
5176 let child = cursor.node();
5177 if child.kind() == "identifier" {
5178 return Some(node_text(source, &child).to_string());
5179 }
5180 if !cursor.goto_next_sibling() {
5181 break;
5182 }
5183 }
5184
5185 None
5186}
5187
5188fn objc_declarator_name(source: &str, node: &Node) -> Option<String> {
5189 if matches!(
5190 node.kind(),
5191 "identifier" | "field_identifier" | "type_identifier"
5192 ) {
5193 return Some(node_text(source, node).to_string());
5194 }
5195
5196 if let Some(declarator) = node.child_by_field_name("declarator") {
5197 if let Some(name) = objc_declarator_name(source, &declarator) {
5198 return Some(name);
5199 }
5200 }
5201
5202 (0..node.child_count()).rev().find_map(|child_index| {
5203 let child = node.child(child_index as u32)?;
5204 if child.is_named() {
5205 objc_declarator_name(source, &child)
5206 } else {
5207 None
5208 }
5209 })
5210}
5211
5212fn toml_key_text(source: &str, key: &Node) -> String {
5213 let raw = node_text(source, key).trim();
5214 if raw.len() >= 2
5215 && ((raw.starts_with('"') && raw.ends_with('"'))
5216 || (raw.starts_with('\'') && raw.ends_with('\'')))
5217 {
5218 raw[1..raw.len() - 1].to_string()
5219 } else {
5220 raw.to_string()
5221 }
5222}
5223
5224fn toml_pair_symbol(source: &str, pair: &Node, section: Option<&str>) -> Option<Symbol> {
5225 let key = pair.child_by_field_name("key").or_else(|| {
5226 let mut cursor = pair.walk();
5227 let first = pair.named_children(&mut cursor).next();
5228 first
5229 })?;
5230 let name = toml_key_text(source, &key);
5231 if name.is_empty() {
5232 return None;
5233 }
5234
5235 let scope_chain = section
5236 .map(|name| vec![name.to_string()])
5237 .unwrap_or_default();
5238 Some(Symbol {
5239 name,
5240 kind: SymbolKind::Variable,
5241 range: node_range_with_decorators(pair, source, LangId::Toml),
5242 signature: None,
5243 scope_chain,
5244 exported: false,
5245 parent: section.map(str::to_string),
5246 })
5247}
5248
5249fn toml_table_name(source: &str, table: &Node) -> Option<String> {
5250 let first_line = node_text(source, table).lines().next()?.trim();
5251 let name = first_line
5252 .strip_prefix("[[")
5253 .and_then(|line| line.strip_suffix("]]"))
5254 .or_else(|| {
5255 first_line
5256 .strip_prefix('[')
5257 .and_then(|line| line.strip_suffix(']'))
5258 })?
5259 .trim();
5260 (!name.is_empty()).then(|| name.to_string())
5261}
5262
5263fn toml_table_range(source: &str, table: &Node) -> Range {
5264 let start = table.start_position();
5265 let text = node_text(source, table).trim_end();
5266 let mut lines = text.lines();
5267 let line_count = lines.clone().count().max(1);
5268 let end_col = lines.next_back().map(str::len).unwrap_or_default();
5269 Range {
5270 start_line: start.row as u32,
5271 start_col: start.column as u32,
5272 end_line: start.row as u32 + line_count as u32 - 1,
5273 end_col: end_col as u32,
5274 }
5275}
5276
5277fn extract_toml_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
5278 let mut symbols = Vec::new();
5279 let mut cursor = root.walk();
5280 for child in root.named_children(&mut cursor) {
5281 match child.kind() {
5282 "pair" => {
5283 if let Some(symbol) = toml_pair_symbol(source, &child, None) {
5284 symbols.push(symbol);
5285 }
5286 }
5287 "table" | "table_array_element" => {
5288 let Some(name) = toml_table_name(source, &child) else {
5289 continue;
5290 };
5291 symbols.push(Symbol {
5292 name: name.clone(),
5293 kind: SymbolKind::Variable,
5294 range: toml_table_range(source, &child),
5295 signature: None,
5296 scope_chain: Vec::new(),
5297 exported: false,
5298 parent: None,
5299 });
5300
5301 let mut table_cursor = child.walk();
5302 for item in child.named_children(&mut table_cursor) {
5303 if item.kind() == "pair" {
5304 if let Some(symbol) = toml_pair_symbol(source, &item, Some(&name)) {
5305 symbols.push(symbol);
5306 }
5307 }
5308 }
5309 }
5310 _ => {}
5311 }
5312 }
5313 Ok(symbols)
5314}
5315
5316pub(crate) fn json_document_value<'a>(root: &Node<'a>) -> Option<Node<'a>> {
5322 let mut cursor = root.walk();
5323 let value = root
5324 .named_children(&mut cursor)
5325 .find(|child| child.kind() != "comment");
5326 value
5327}
5328
5329fn extract_json_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
5330 let Some(value) = json_document_value(root) else {
5331 return Ok(Vec::new());
5332 };
5333
5334 if value.kind() != "object" {
5335 return Ok(Vec::new());
5336 }
5337
5338 let mut symbols = Vec::new();
5339 let mut cursor = value.walk();
5340 for child in value.named_children(&mut cursor) {
5341 if child.kind() != "pair" {
5342 continue;
5343 }
5344 let Some(key_node) = child.child_by_field_name("key") else {
5345 continue;
5346 };
5347 let name = node_text(source, &key_node).trim_matches('"').to_string();
5348 if name.is_empty() {
5349 continue;
5350 }
5351 symbols.push(Symbol {
5352 name,
5353 kind: SymbolKind::Variable,
5354 range: node_range_with_decorators(&child, source, LangId::Json),
5355 signature: None,
5356 scope_chain: vec![],
5357 exported: false,
5358 parent: None,
5359 });
5360 }
5361
5362 Ok(symbols)
5363}
5364
5365fn yaml_scalar_text(source: &str, node: &Node) -> String {
5367 node_text(source, node)
5368 .trim()
5369 .trim_matches('"')
5370 .trim_matches('\'')
5371 .to_string()
5372}
5373
5374fn yaml_find_mapping<'a>(node: &Node<'a>) -> Option<Node<'a>> {
5380 let mut current = *node;
5381 loop {
5382 match current.kind() {
5383 "block_mapping" | "flow_mapping" => return Some(current),
5384 "document" | "block_node" | "flow_node" => {
5385 let mut cursor = current.walk();
5386 let next = current
5387 .named_children(&mut cursor)
5388 .find(|child| !matches!(child.kind(), "tag" | "anchor"));
5389 match next {
5390 Some(inner) => current = inner,
5391 None => return None,
5392 }
5393 }
5394 _ => return None,
5395 }
5396 }
5397}
5398
5399fn yaml_get<'a>(mapping: &Node<'a>, source: &str, key: &str) -> Option<Node<'a>> {
5401 let mut cursor = mapping.walk();
5402 for pair in mapping.named_children(&mut cursor) {
5403 if pair.kind() != "block_mapping_pair" && pair.kind() != "flow_pair" {
5404 continue;
5405 }
5406 let Some(key_node) = pair.child_by_field_name("key") else {
5407 continue;
5408 };
5409 if yaml_scalar_text(source, &key_node) == key {
5410 return pair.child_by_field_name("value");
5411 }
5412 }
5413 None
5414}
5415
5416fn yaml_unwrap<'a>(node: &Node<'a>) -> Node<'a> {
5421 let mut current = *node;
5422 while current.kind() == "block_node" || current.kind() == "flow_node" {
5423 let mut cursor = current.walk();
5424 let next = current
5425 .named_children(&mut cursor)
5426 .find(|child| !matches!(child.kind(), "tag" | "anchor"));
5427 match next {
5428 Some(inner) => current = inner,
5429 None => break,
5430 }
5431 }
5432 current
5433}
5434
5435fn yaml_flatten_value(source: &str, node: &Node) -> String {
5436 let node = &yaml_unwrap(node);
5437 match node.kind() {
5438 "block_sequence" | "flow_sequence" => {
5439 let mut items = Vec::new();
5440 let mut cursor = node.walk();
5441 for child in node.named_children(&mut cursor) {
5442 let value = if child.kind() == "block_sequence_item" {
5444 child.named_child(0)
5445 } else {
5446 Some(child)
5447 };
5448 if let Some(v) = value {
5449 let v = yaml_unwrap(&v);
5450 if v.kind() != "block_mapping" && v.kind() != "flow_mapping" {
5452 let text = yaml_scalar_text(source, &v);
5453 if !text.is_empty() {
5454 items.push(text);
5455 }
5456 }
5457 }
5458 }
5459 items.join(",")
5460 }
5461 "block_mapping" | "flow_mapping" => String::new(),
5465 _ => yaml_scalar_text(source, node),
5466 }
5467}
5468
5469fn yaml_collect_values(
5473 source: &str,
5474 node: &Node,
5475 keys: &[&str],
5476 out: &mut Vec<String>,
5477 cap: usize,
5478) {
5479 if out.len() >= cap {
5480 return;
5481 }
5482 let mut cursor = node.walk();
5483 for child in node.named_children(&mut cursor) {
5484 if child.kind() == "block_mapping_pair" || child.kind() == "flow_pair" {
5485 if let Some(key_node) = child.child_by_field_name("key") {
5486 let key_text = yaml_scalar_text(source, &key_node);
5487 if keys.contains(&key_text.as_str()) {
5488 if let Some(value_node) = child.child_by_field_name("value") {
5489 let value_text = yaml_flatten_value(source, &value_node);
5490 if !value_text.is_empty() && out.len() < cap {
5491 out.push(format!("{}={}", key_text, value_text));
5492 }
5493 }
5494 }
5495 }
5496 }
5497 yaml_collect_values(source, &child, keys, out, cap);
5498 }
5499}
5500
5501fn yaml_collect_named_items(
5507 source: &str,
5508 node: &Node,
5509 parent_key: &str,
5510 label: &str,
5511 out: &mut Vec<String>,
5512 cap: usize,
5513) {
5514 let mut cursor = node.walk();
5515 for child in node.named_children(&mut cursor) {
5516 if (child.kind() == "block_mapping_pair" || child.kind() == "flow_pair")
5517 && child
5518 .child_by_field_name("key")
5519 .map(|k| yaml_scalar_text(source, &k))
5520 .as_deref()
5521 == Some(parent_key)
5522 {
5523 if let Some(seq) = child.child_by_field_name("value") {
5524 let seq = yaml_unwrap(&seq);
5525 let mut names = Vec::new();
5526 let mut seq_cursor = seq.walk();
5527 for item in seq.named_children(&mut seq_cursor) {
5528 let value = if item.kind() == "block_sequence_item" {
5529 item.named_child(0)
5530 } else {
5531 Some(item)
5532 };
5533 if let Some(v) = value {
5534 if let Some(map) = yaml_find_mapping(&v) {
5535 if let Some(name_node) = yaml_get(&map, source, "name") {
5536 let n = yaml_scalar_text(source, &name_node);
5537 if !n.is_empty() && names.len() < 16 {
5538 names.push(n);
5539 }
5540 }
5541 }
5542 }
5543 }
5544 if !names.is_empty() && out.len() < cap {
5545 out.push(format!("{}={}", label, names.join(",")));
5546 }
5547 }
5548 }
5549 yaml_collect_named_items(source, &child, parent_key, label, out, cap);
5550 }
5551}
5552
5553fn yaml_k8s_resource_symbol(source: &str, doc: &Node, mapping: &Node) -> Option<Symbol> {
5557 let api_version =
5558 yaml_get(mapping, source, "apiVersion").map(|n| yaml_scalar_text(source, &n))?;
5559 let kind = yaml_get(mapping, source, "kind").map(|n| yaml_scalar_text(source, &n))?;
5560 if api_version.is_empty() || kind.is_empty() {
5561 return None;
5562 }
5563
5564 let (name, namespace) = match yaml_get(mapping, source, "metadata") {
5565 Some(meta) => match yaml_find_mapping(&meta) {
5566 Some(meta_map) => {
5567 let name = yaml_get(&meta_map, source, "name")
5571 .map(|n| yaml_scalar_text(source, &n))
5572 .filter(|s| !s.is_empty())
5573 .or_else(|| {
5574 yaml_get(&meta_map, source, "generateName")
5575 .map(|n| yaml_scalar_text(source, &n))
5576 .filter(|s| !s.is_empty())
5577 });
5578 (
5579 name,
5580 yaml_get(&meta_map, source, "namespace").map(|n| yaml_scalar_text(source, &n)),
5581 )
5582 }
5583 None => (None, None),
5584 },
5585 None => (None, None),
5586 };
5587
5588 let res_name = name.clone().filter(|s| !s.is_empty());
5589 let sym_name = match (&namespace, &res_name) {
5590 (Some(ns), Some(n)) if !ns.is_empty() => format!("{}/{}/{}", ns, kind, n),
5591 (_, Some(n)) => format!("{}/{}", kind, n),
5592 _ => kind.clone(),
5593 };
5594
5595 let mut sig = format!("apiVersion={} kind={}", api_version, kind);
5596 if let Some(ns) = namespace.as_ref().filter(|s| !s.is_empty()) {
5597 sig.push_str(&format!(" namespace={}", ns));
5598 }
5599 if let Some(n) = res_name.as_ref() {
5600 sig.push_str(&format!(" name={}", n));
5601 }
5602 let mut extras = Vec::new();
5606 yaml_collect_values(
5607 source,
5608 mapping,
5609 &[
5610 "image",
5611 "containerPort",
5612 "port",
5613 "targetPort",
5614 "cpu",
5615 "memory",
5616 "storage",
5617 "verbs",
5618 "resources",
5619 "apiGroups",
5620 "mountPath",
5621 "replicas",
5622 "entrypoint",
5624 "command",
5625 "args",
5626 "schedule",
5627 ],
5628 &mut extras,
5629 24,
5630 );
5631 yaml_collect_named_items(source, mapping, "env", "env", &mut extras, 24);
5634 yaml_collect_named_items(source, mapping, "templates", "templates", &mut extras, 24);
5635 if !extras.is_empty() {
5636 sig.push(' ');
5637 sig.push_str(&extras.join(" "));
5638 }
5639
5640 Some(Symbol {
5641 name: sym_name,
5642 kind: SymbolKind::Class,
5643 range: node_range(doc),
5644 signature: Some(sig),
5645 scope_chain: vec![],
5646 exported: true,
5647 parent: None,
5648 })
5649}
5650
5651fn yaml_generic_keys(source: &str, mapping: &Node, symbols: &mut Vec<Symbol>) {
5654 let mut cursor = mapping.walk();
5655 for pair in mapping.named_children(&mut cursor) {
5656 if pair.kind() != "block_mapping_pair" && pair.kind() != "flow_pair" {
5657 continue;
5658 }
5659 let Some(key_node) = pair.child_by_field_name("key") else {
5660 continue;
5661 };
5662 let name = yaml_scalar_text(source, &key_node);
5663 if name.is_empty() {
5664 continue;
5665 }
5666 symbols.push(Symbol {
5667 name,
5668 kind: SymbolKind::Variable,
5669 range: node_range(&pair),
5670 signature: None,
5671 scope_chain: vec![],
5672 exported: false,
5673 parent: None,
5674 });
5675 }
5676}
5677
5678fn extract_yaml_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
5684 let mut symbols = Vec::new();
5685 let mut cursor = root.walk();
5686 for doc in root.named_children(&mut cursor) {
5687 if doc.kind() != "document" {
5688 continue;
5689 }
5690 let Some(mapping) = yaml_find_mapping(&doc) else {
5691 continue;
5692 };
5693 if let Some(symbol) = yaml_k8s_resource_symbol(source, &doc, &mapping) {
5694 symbols.push(symbol);
5695 } else {
5696 yaml_generic_keys(source, &mapping, &mut symbols);
5697 }
5698 }
5699 Ok(symbols)
5700}
5701
5702fn scala_scope_chain(node: &Node, source: &str) -> Vec<String> {
5703 let mut chain = Vec::new();
5704 let mut current = node.parent();
5705
5706 while let Some(parent) = current {
5707 match parent.kind() {
5708 "class_definition" | "object_definition" | "enum_definition" | "trait_definition" => {
5709 if let Some(name_node) = parent.child_by_field_name("name") {
5710 chain.push(node_text(source, &name_node).to_string());
5711 }
5712 }
5713 _ => {}
5714 }
5715 current = parent.parent();
5716 }
5717
5718 chain.reverse();
5719 chain
5720}
5721
5722fn extract_scala_symbols(
5723 source: &str,
5724 root: &Node,
5725 query: &Query,
5726) -> Result<Vec<Symbol>, AftError> {
5727 let lang = LangId::Scala;
5728 let capture_names = query.capture_names();
5729
5730 let mut symbols = Vec::new();
5731 let mut cursor = QueryCursor::new();
5732 let mut matches = cursor.matches(query, *root, source.as_bytes());
5733
5734 while let Some(m) = {
5735 matches.advance();
5736 matches.get()
5737 } {
5738 let mut class_name_node = None;
5739 let mut class_def_node = None;
5740 let mut object_name_node = None;
5741 let mut object_def_node = None;
5742 let mut enum_name_node = None;
5743 let mut enum_def_node = None;
5744 let mut trait_name_node = None;
5745 let mut trait_def_node = None;
5746 let mut fn_name_node = None;
5747 let mut fn_def_node = None;
5748 let mut val_name_node = None;
5749 let mut val_def_node = None;
5750 let mut var_name_node = None;
5751 let mut var_def_node = None;
5752 let mut type_name_node = None;
5753 let mut type_def_node = None;
5754
5755 for cap in m.captures {
5756 let Some(&name) = capture_names.get(cap.index as usize) else {
5757 continue;
5758 };
5759 match name {
5760 "class.name" => class_name_node = Some(cap.node),
5761 "class.def" => class_def_node = Some(cap.node),
5762 "object.name" => object_name_node = Some(cap.node),
5763 "object.def" => object_def_node = Some(cap.node),
5764 "enum.name" => enum_name_node = Some(cap.node),
5765 "enum.def" => enum_def_node = Some(cap.node),
5766 "trait.name" => trait_name_node = Some(cap.node),
5767 "trait.def" => trait_def_node = Some(cap.node),
5768 "fn.name" => fn_name_node = Some(cap.node),
5769 "fn.def" => fn_def_node = Some(cap.node),
5770 "val.name" => val_name_node = Some(cap.node),
5771 "val.def" => val_def_node = Some(cap.node),
5772 "var.name" => var_name_node = Some(cap.node),
5773 "var.def" => var_def_node = Some(cap.node),
5774 "given.name" => val_name_node = Some(cap.node),
5775 "given.def" => val_def_node = Some(cap.node),
5776 "type.name" => type_name_node = Some(cap.node),
5777 "type.def" => type_def_node = Some(cap.node),
5778 _ => {}
5779 }
5780 }
5781
5782 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
5783 symbols.push(Symbol {
5784 name: node_text(source, &name_node).to_string(),
5785 kind: SymbolKind::Class,
5786 range: node_range_with_decorators(&def_node, source, lang),
5787 signature: Some(extract_signature(source, &def_node)),
5788 scope_chain: scala_scope_chain(&def_node, source),
5789 exported: true,
5790 parent: scala_scope_chain(&def_node, source).last().cloned(),
5791 });
5792 }
5793
5794 if let (Some(name_node), Some(def_node)) = (object_name_node, object_def_node) {
5795 symbols.push(Symbol {
5796 name: node_text(source, &name_node).to_string(),
5797 kind: SymbolKind::Class,
5798 range: node_range_with_decorators(&def_node, source, lang),
5799 signature: Some(extract_signature(source, &def_node)),
5800 scope_chain: scala_scope_chain(&def_node, source),
5801 exported: true,
5802 parent: scala_scope_chain(&def_node, source).last().cloned(),
5803 });
5804 }
5805
5806 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
5807 symbols.push(Symbol {
5808 name: node_text(source, &name_node).to_string(),
5809 kind: SymbolKind::Enum,
5810 range: node_range_with_decorators(&def_node, source, lang),
5811 signature: Some(extract_signature(source, &def_node)),
5812 scope_chain: scala_scope_chain(&def_node, source),
5813 exported: true,
5814 parent: scala_scope_chain(&def_node, source).last().cloned(),
5815 });
5816 }
5817
5818 if let (Some(name_node), Some(def_node)) = (trait_name_node, trait_def_node) {
5819 symbols.push(Symbol {
5820 name: node_text(source, &name_node).to_string(),
5821 kind: SymbolKind::Interface,
5822 range: node_range_with_decorators(&def_node, source, lang),
5823 signature: Some(extract_signature(source, &def_node)),
5824 scope_chain: scala_scope_chain(&def_node, source),
5825 exported: true,
5826 parent: scala_scope_chain(&def_node, source).last().cloned(),
5827 });
5828 }
5829
5830 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
5831 let scope_chain = scala_scope_chain(&def_node, source);
5832 let kind = if scope_chain.is_empty() {
5833 SymbolKind::Function
5834 } else {
5835 SymbolKind::Method
5836 };
5837 symbols.push(Symbol {
5838 name: node_text(source, &name_node).to_string(),
5839 kind,
5840 range: node_range_with_decorators(&def_node, source, lang),
5841 signature: Some(extract_signature(source, &def_node)),
5842 parent: scope_chain.last().cloned(),
5843 scope_chain,
5844 exported: true,
5845 });
5846 }
5847
5848 if let (Some(name_node), Some(def_node)) = (val_name_node, val_def_node) {
5849 let scope_chain = scala_scope_chain(&def_node, source);
5850 symbols.push(Symbol {
5851 name: node_text(source, &name_node).to_string(),
5852 kind: SymbolKind::Variable,
5853 range: node_range_with_decorators(&def_node, source, lang),
5854 signature: Some(extract_signature(source, &def_node)),
5855 parent: scope_chain.last().cloned(),
5856 scope_chain,
5857 exported: true,
5858 });
5859 }
5860
5861 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
5862 let scope_chain = scala_scope_chain(&def_node, source);
5863 symbols.push(Symbol {
5864 name: node_text(source, &name_node).to_string(),
5865 kind: SymbolKind::Variable,
5866 range: node_range_with_decorators(&def_node, source, lang),
5867 signature: Some(extract_signature(source, &def_node)),
5868 parent: scope_chain.last().cloned(),
5869 scope_chain,
5870 exported: true,
5871 });
5872 }
5873
5874 if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
5875 let scope_chain = scala_scope_chain(&def_node, source);
5876 symbols.push(Symbol {
5877 name: node_text(source, &name_node).to_string(),
5878 kind: SymbolKind::TypeAlias,
5879 range: node_range_with_decorators(&def_node, source, lang),
5880 signature: Some(extract_signature(source, &def_node)),
5881 parent: scope_chain.last().cloned(),
5882 scope_chain,
5883 exported: true,
5884 });
5885 }
5886 }
5887
5888 dedup_symbols(&mut symbols);
5889 Ok(symbols)
5890}
5891
5892fn child_text_by_field_or_kind(
5893 node: &Node,
5894 source: &str,
5895 field_name: &str,
5896 kinds: &[&str],
5897) -> Option<String> {
5898 if let Some(name_node) = node.child_by_field_name(field_name) {
5899 return Some(node_text(source, &name_node).to_string());
5900 }
5901
5902 let mut cursor = node.walk();
5903 if !cursor.goto_first_child() {
5904 return None;
5905 }
5906
5907 loop {
5908 let child = cursor.node();
5909 if kinds.contains(&child.kind()) {
5910 return Some(node_text(source, &child).to_string());
5911 }
5912 if !cursor.goto_next_sibling() {
5913 break;
5914 }
5915 }
5916
5917 None
5918}
5919
5920fn push_captured_symbol(
5921 symbols: &mut Vec<Symbol>,
5922 source: &str,
5923 lang: LangId,
5924 name_node: Node,
5925 def_node: Node,
5926 kind: SymbolKind,
5927 scope_chain: Vec<String>,
5928 exported: bool,
5929) {
5930 symbols.push(Symbol {
5931 name: node_text(source, &name_node).to_string(),
5932 kind,
5933 range: node_range_with_decorators(&def_node, source, lang),
5934 signature: Some(extract_signature(source, &def_node)),
5935 parent: scope_chain.last().cloned(),
5936 scope_chain,
5937 exported,
5938 });
5939}
5940
5941fn range_spanning_nodes(start_node: &Node, end_node: &Node) -> Range {
5942 let start = start_node.start_position();
5943 let end = end_node.end_position();
5944 Range {
5945 start_line: start.row as u32,
5946 start_col: start.column as u32,
5947 end_line: end.row as u32,
5948 end_col: end.column as u32,
5949 }
5950}
5951
5952fn extract_signature_between(source: &str, start_node: &Node, end_node: &Node) -> String {
5953 let start = start_node.start_byte();
5954 let end = end_node.end_byte();
5955 let text = source.get(start..end).unwrap_or_default();
5956 let first_line = text.lines().next().unwrap_or(text);
5957 let trimmed = first_line.trim_end();
5958 let trimmed = trimmed.strip_suffix('{').unwrap_or(trimmed).trim_end();
5959 trimmed.to_string()
5960}
5961
5962fn groovy_scope_chain(node: &Node, source: &str) -> Vec<String> {
5963 let mut chain = Vec::new();
5964 let mut current = node.parent();
5965
5966 while let Some(parent) = current {
5967 if matches!(
5968 parent.kind(),
5969 "class_declaration"
5970 | "interface_declaration"
5971 | "trait_declaration"
5972 | "enum_declaration"
5973 | "record_declaration"
5974 | "annotation_type_declaration"
5975 ) {
5976 if let Some(name_node) = parent.child_by_field_name("name") {
5977 chain.push(node_text(source, &name_node).to_string());
5978 }
5979 }
5980 current = parent.parent();
5981 }
5982
5983 chain.reverse();
5984 chain
5985}
5986
5987fn groovy_keyword_task_symbol(
5991 source: &str,
5992 keyword_stmt: Node,
5993 declaration: Node,
5994) -> Option<Symbol> {
5995 if keyword_stmt.kind() != "expression_statement" || declaration.kind() != "command_chain" {
5996 return None;
5997 }
5998
5999 let keyword = keyword_stmt.named_child(0)?;
6000 if keyword.kind() != "identifier" || node_text(source, &keyword) != "task" {
6001 return None;
6002 }
6003
6004 let name_node = declaration.child_by_field_name("receiver")?;
6005 if name_node.kind() != "identifier" {
6006 return None;
6007 }
6008
6009 let body_node = declaration.child_by_field_name("argument")?;
6010 if body_node.kind() != "closure" {
6011 return None;
6012 }
6013
6014 Some(Symbol {
6015 name: node_text(source, &name_node).to_string(),
6016 kind: SymbolKind::Function,
6017 range: range_spanning_nodes(&keyword_stmt, &declaration),
6018 signature: Some(extract_signature_between(
6019 source,
6020 &keyword_stmt,
6021 &declaration,
6022 )),
6023 scope_chain: vec![],
6024 exported: true,
6025 parent: None,
6026 })
6027}
6028
6029fn groovy_registered_task_symbol(
6033 source: &str,
6034 invocation_stmt: Node,
6035 closure_stmt: Node,
6036) -> Option<Symbol> {
6037 if invocation_stmt.kind() != "expression_statement"
6038 || closure_stmt.kind() != "expression_statement"
6039 {
6040 return None;
6041 }
6042
6043 let invocation = invocation_stmt.named_child(0)?;
6044 if invocation.kind() != "method_invocation" {
6045 return None;
6046 }
6047
6048 let function = invocation.child_by_field_name("function")?;
6049 if function.kind() != "field_access" {
6050 return None;
6051 }
6052
6053 let object = function.child_by_field_name("object")?;
6054 let field = function.child_by_field_name("field")?;
6055 if node_text(source, &object) != "tasks" || node_text(source, &field) != "register" {
6056 return None;
6057 }
6058
6059 let arguments = invocation.child_by_field_name("arguments")?;
6060 let name_node = arguments.named_child(0)?;
6061 if name_node.kind() != "string_literal" {
6062 return None;
6063 }
6064
6065 let closure = closure_stmt.named_child(0)?;
6066 if closure.kind() != "closure" {
6067 return None;
6068 }
6069
6070 Some(Symbol {
6071 name: string_content(source, &name_node)?,
6072 kind: SymbolKind::Function,
6073 range: range_spanning_nodes(&invocation_stmt, &closure_stmt),
6074 signature: Some(extract_signature_between(
6075 source,
6076 &invocation_stmt,
6077 &closure_stmt,
6078 )),
6079 scope_chain: vec![],
6080 exported: true,
6081 parent: None,
6082 })
6083}
6084
6085fn collect_groovy_task_symbols(source: &str, root: &Node, symbols: &mut Vec<Symbol>) {
6086 let children: Vec<Node> = root.named_children(&mut root.walk()).collect();
6087 let mut index = 0;
6088 while index + 1 < children.len() {
6089 if let Some(symbol) =
6090 groovy_keyword_task_symbol(source, children[index], children[index + 1])
6091 {
6092 symbols.push(symbol);
6093 index += 2;
6094 continue;
6095 }
6096
6097 if let Some(symbol) =
6098 groovy_registered_task_symbol(source, children[index], children[index + 1])
6099 {
6100 symbols.push(symbol);
6101 index += 2;
6102 continue;
6103 }
6104
6105 index += 1;
6106 }
6107}
6108
6109fn java_scope_chain(node: &Node, source: &str) -> Vec<String> {
6110 let mut chain = Vec::new();
6111 let mut current = node.parent();
6112
6113 while let Some(parent) = current {
6114 if matches!(
6115 parent.kind(),
6116 "class_declaration"
6117 | "interface_declaration"
6118 | "annotation_type_declaration"
6119 | "enum_declaration"
6120 | "record_declaration"
6121 ) {
6122 if let Some(name_node) = parent.child_by_field_name("name") {
6123 chain.push(node_text(source, &name_node).to_string());
6124 }
6125 }
6126 current = parent.parent();
6127 }
6128
6129 chain.reverse();
6130 chain
6131}
6132
6133fn extract_java_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
6134 let lang = LangId::Java;
6135 let capture_names = query.capture_names();
6136 let mut symbols = Vec::new();
6137 let mut cursor = QueryCursor::new();
6138 let mut matches = cursor.matches(query, *root, source.as_bytes());
6139
6140 while let Some(m) = {
6141 matches.advance();
6142 matches.get()
6143 } {
6144 let mut class_name_node = None;
6145 let mut class_def_node = None;
6146 let mut interface_name_node = None;
6147 let mut interface_def_node = None;
6148 let mut enum_name_node = None;
6149 let mut enum_def_node = None;
6150 let mut struct_name_node = None;
6151 let mut struct_def_node = None;
6152 let mut fn_name_node = None;
6153 let mut fn_def_node = None;
6154 let mut var_name_node = None;
6155 let mut var_def_node = None;
6156
6157 for cap in m.captures {
6158 let Some(&name) = capture_names.get(cap.index as usize) else {
6159 continue;
6160 };
6161 match name {
6162 "class.name" => class_name_node = Some(cap.node),
6163 "class.def" => class_def_node = Some(cap.node),
6164 "interface.name" => interface_name_node = Some(cap.node),
6165 "interface.def" => interface_def_node = Some(cap.node),
6166 "enum.name" => enum_name_node = Some(cap.node),
6167 "enum.def" => enum_def_node = Some(cap.node),
6168 "struct.name" => struct_name_node = Some(cap.node),
6169 "struct.def" => struct_def_node = Some(cap.node),
6170 "fn.name" => fn_name_node = Some(cap.node),
6171 "fn.def" => fn_def_node = Some(cap.node),
6172 "var.name" => var_name_node = Some(cap.node),
6173 "var.def" => var_def_node = Some(cap.node),
6174 _ => {}
6175 }
6176 }
6177
6178 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
6179 push_captured_symbol(
6180 &mut symbols,
6181 source,
6182 lang,
6183 name_node,
6184 def_node,
6185 SymbolKind::Class,
6186 java_scope_chain(&def_node, source),
6187 true,
6188 );
6189 }
6190 if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
6191 push_captured_symbol(
6192 &mut symbols,
6193 source,
6194 lang,
6195 name_node,
6196 def_node,
6197 SymbolKind::Interface,
6198 java_scope_chain(&def_node, source),
6199 true,
6200 );
6201 }
6202 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
6203 push_captured_symbol(
6204 &mut symbols,
6205 source,
6206 lang,
6207 name_node,
6208 def_node,
6209 SymbolKind::Enum,
6210 java_scope_chain(&def_node, source),
6211 true,
6212 );
6213 }
6214 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
6215 push_captured_symbol(
6216 &mut symbols,
6217 source,
6218 lang,
6219 name_node,
6220 def_node,
6221 SymbolKind::Struct,
6222 java_scope_chain(&def_node, source),
6223 true,
6224 );
6225 }
6226 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
6227 let scope_chain = java_scope_chain(&def_node, source);
6228 let kind = if scope_chain.is_empty() {
6229 SymbolKind::Function
6230 } else {
6231 SymbolKind::Method
6232 };
6233 push_captured_symbol(
6234 &mut symbols,
6235 source,
6236 lang,
6237 name_node,
6238 def_node,
6239 kind,
6240 scope_chain,
6241 true,
6242 );
6243 }
6244 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
6245 push_captured_symbol(
6246 &mut symbols,
6247 source,
6248 lang,
6249 name_node,
6250 def_node,
6251 SymbolKind::Variable,
6252 java_scope_chain(&def_node, source),
6253 true,
6254 );
6255 }
6256 }
6257
6258 dedup_symbols(&mut symbols);
6259 Ok(symbols)
6260}
6261
6262fn ruby_scope_chain(node: &Node, source: &str) -> Vec<String> {
6263 let mut chain = Vec::new();
6264 let mut current = node.parent();
6265
6266 while let Some(parent) = current {
6267 if matches!(parent.kind(), "class" | "module") {
6268 if let Some(name_node) = parent.child_by_field_name("name") {
6269 chain.push(node_text(source, &name_node).to_string());
6270 }
6271 }
6272 current = parent.parent();
6273 }
6274
6275 chain.reverse();
6276 chain
6277}
6278
6279fn extract_ruby_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
6280 let lang = LangId::Ruby;
6281 let capture_names = query.capture_names();
6282 let mut symbols = Vec::new();
6283 let mut cursor = QueryCursor::new();
6284 let mut matches = cursor.matches(query, *root, source.as_bytes());
6285
6286 while let Some(m) = {
6287 matches.advance();
6288 matches.get()
6289 } {
6290 let mut module_name_node = None;
6291 let mut module_def_node = None;
6292 let mut class_name_node = None;
6293 let mut class_def_node = None;
6294 let mut fn_name_node = None;
6295 let mut fn_def_node = None;
6296 let mut var_name_node = None;
6297 let mut var_def_node = None;
6298
6299 for cap in m.captures {
6300 let Some(&name) = capture_names.get(cap.index as usize) else {
6301 continue;
6302 };
6303 match name {
6304 "module.name" => module_name_node = Some(cap.node),
6305 "module.def" => module_def_node = Some(cap.node),
6306 "class.name" => class_name_node = Some(cap.node),
6307 "class.def" => class_def_node = Some(cap.node),
6308 "fn.name" => fn_name_node = Some(cap.node),
6309 "fn.def" => fn_def_node = Some(cap.node),
6310 "var.name" => var_name_node = Some(cap.node),
6311 "var.def" => var_def_node = Some(cap.node),
6312 _ => {}
6313 }
6314 }
6315
6316 if let (Some(name_node), Some(def_node)) = (module_name_node, module_def_node) {
6317 push_captured_symbol(
6318 &mut symbols,
6319 source,
6320 lang,
6321 name_node,
6322 def_node,
6323 SymbolKind::Class,
6324 ruby_scope_chain(&def_node, source),
6325 true,
6326 );
6327 }
6328 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
6329 push_captured_symbol(
6330 &mut symbols,
6331 source,
6332 lang,
6333 name_node,
6334 def_node,
6335 SymbolKind::Class,
6336 ruby_scope_chain(&def_node, source),
6337 true,
6338 );
6339 }
6340 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
6341 let scope_chain = ruby_scope_chain(&def_node, source);
6342 let kind = if scope_chain.is_empty() {
6343 SymbolKind::Function
6344 } else {
6345 SymbolKind::Method
6346 };
6347 push_captured_symbol(
6348 &mut symbols,
6349 source,
6350 lang,
6351 name_node,
6352 def_node,
6353 kind,
6354 scope_chain,
6355 true,
6356 );
6357 }
6358 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
6359 push_captured_symbol(
6360 &mut symbols,
6361 source,
6362 lang,
6363 name_node,
6364 def_node,
6365 SymbolKind::Variable,
6366 ruby_scope_chain(&def_node, source),
6367 true,
6368 );
6369 }
6370 }
6371
6372 dedup_symbols(&mut symbols);
6373 Ok(symbols)
6374}
6375
6376fn kotlin_scope_chain(node: &Node, source: &str) -> Vec<String> {
6377 let mut chain = Vec::new();
6378 let mut current = node.parent();
6379
6380 while let Some(parent) = current {
6381 if matches!(parent.kind(), "class_declaration" | "object_declaration") {
6382 if let Some(name) =
6383 child_text_by_field_or_kind(&parent, source, "name", &["type_identifier"])
6384 {
6385 chain.push(name);
6386 }
6387 }
6388 current = parent.parent();
6389 }
6390
6391 chain.reverse();
6392 chain
6393}
6394
6395fn extract_kotlin_symbols(
6396 source: &str,
6397 root: &Node,
6398 query: &Query,
6399) -> Result<Vec<Symbol>, AftError> {
6400 let lang = LangId::Kotlin;
6401 let capture_names = query.capture_names();
6402 let mut symbols = Vec::new();
6403 let mut cursor = QueryCursor::new();
6404 let mut matches = cursor.matches(query, *root, source.as_bytes());
6405
6406 while let Some(m) = {
6407 matches.advance();
6408 matches.get()
6409 } {
6410 let mut class_name_node = None;
6411 let mut class_def_node = None;
6412 let mut object_name_node = None;
6413 let mut object_def_node = None;
6414 let mut fn_name_node = None;
6415 let mut fn_def_node = None;
6416 let mut var_name_node = None;
6417 let mut var_def_node = None;
6418 let mut type_name_node = None;
6419 let mut type_def_node = None;
6420
6421 for cap in m.captures {
6422 let Some(&name) = capture_names.get(cap.index as usize) else {
6423 continue;
6424 };
6425 match name {
6426 "class.name" => class_name_node = Some(cap.node),
6427 "class.def" => class_def_node = Some(cap.node),
6428 "object.name" => object_name_node = Some(cap.node),
6429 "object.def" => object_def_node = Some(cap.node),
6430 "fn.name" => fn_name_node = Some(cap.node),
6431 "fn.def" => fn_def_node = Some(cap.node),
6432 "var.name" => var_name_node = Some(cap.node),
6433 "var.def" => var_def_node = Some(cap.node),
6434 "type.name" => type_name_node = Some(cap.node),
6435 "type.def" => type_def_node = Some(cap.node),
6436 _ => {}
6437 }
6438 }
6439
6440 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
6441 push_captured_symbol(
6442 &mut symbols,
6443 source,
6444 lang,
6445 name_node,
6446 def_node,
6447 SymbolKind::Class,
6448 kotlin_scope_chain(&def_node, source),
6449 true,
6450 );
6451 }
6452 if let (Some(name_node), Some(def_node)) = (object_name_node, object_def_node) {
6453 push_captured_symbol(
6454 &mut symbols,
6455 source,
6456 lang,
6457 name_node,
6458 def_node,
6459 SymbolKind::Class,
6460 kotlin_scope_chain(&def_node, source),
6461 true,
6462 );
6463 }
6464 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
6465 let scope_chain = kotlin_scope_chain(&def_node, source);
6466 let kind = if scope_chain.is_empty() {
6467 SymbolKind::Function
6468 } else {
6469 SymbolKind::Method
6470 };
6471 push_captured_symbol(
6472 &mut symbols,
6473 source,
6474 lang,
6475 name_node,
6476 def_node,
6477 kind,
6478 scope_chain,
6479 true,
6480 );
6481 }
6482 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
6483 push_captured_symbol(
6484 &mut symbols,
6485 source,
6486 lang,
6487 name_node,
6488 def_node,
6489 SymbolKind::Variable,
6490 kotlin_scope_chain(&def_node, source),
6491 true,
6492 );
6493 }
6494 if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
6495 push_captured_symbol(
6496 &mut symbols,
6497 source,
6498 lang,
6499 name_node,
6500 def_node,
6501 SymbolKind::TypeAlias,
6502 kotlin_scope_chain(&def_node, source),
6503 true,
6504 );
6505 }
6506 }
6507
6508 dedup_symbols(&mut symbols);
6509 Ok(symbols)
6510}
6511
6512fn swift_scope_chain(node: &Node, source: &str) -> Vec<String> {
6513 let mut chain = Vec::new();
6514 let mut current = node.parent();
6515
6516 while let Some(parent) = current {
6517 if matches!(parent.kind(), "class_declaration" | "protocol_declaration") {
6518 if let Some(name_node) = parent.child_by_field_name("name") {
6519 chain.push(node_text(source, &name_node).to_string());
6520 }
6521 }
6522 current = parent.parent();
6523 }
6524
6525 chain.reverse();
6526 chain
6527}
6528
6529fn swift_type_kind(source: &str, node: &Node) -> SymbolKind {
6530 match node
6531 .child_by_field_name("declaration_kind")
6532 .map(|kind_node| node_text(source, &kind_node))
6533 {
6534 Some("struct") => SymbolKind::Struct,
6535 Some("enum") => SymbolKind::Enum,
6536 _ => SymbolKind::Class,
6537 }
6538}
6539
6540fn extract_swift_symbols(
6541 source: &str,
6542 root: &Node,
6543 query: &Query,
6544) -> Result<Vec<Symbol>, AftError> {
6545 let lang = LangId::Swift;
6546 let capture_names = query.capture_names();
6547 let mut symbols = Vec::new();
6548 let mut cursor = QueryCursor::new();
6549 let mut matches = cursor.matches(query, *root, source.as_bytes());
6550
6551 while let Some(m) = {
6552 matches.advance();
6553 matches.get()
6554 } {
6555 let mut class_name_node = None;
6556 let mut class_def_node = None;
6557 let mut interface_name_node = None;
6558 let mut interface_def_node = None;
6559 let mut fn_name_node = None;
6560 let mut fn_def_node = None;
6561 let mut var_name_node = None;
6562 let mut var_def_node = None;
6563 let mut type_name_node = None;
6564 let mut type_def_node = None;
6565
6566 for cap in m.captures {
6567 let Some(&name) = capture_names.get(cap.index as usize) else {
6568 continue;
6569 };
6570 match name {
6571 "class.name" => class_name_node = Some(cap.node),
6572 "class.def" => class_def_node = Some(cap.node),
6573 "interface.name" => interface_name_node = Some(cap.node),
6574 "interface.def" => interface_def_node = Some(cap.node),
6575 "fn.name" => fn_name_node = Some(cap.node),
6576 "fn.def" => fn_def_node = Some(cap.node),
6577 "var.name" => var_name_node = Some(cap.node),
6578 "var.def" => var_def_node = Some(cap.node),
6579 "type.name" => type_name_node = Some(cap.node),
6580 "type.def" => type_def_node = Some(cap.node),
6581 _ => {}
6582 }
6583 }
6584
6585 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
6586 let kind = swift_type_kind(source, &def_node);
6587 push_captured_symbol(
6588 &mut symbols,
6589 source,
6590 lang,
6591 name_node,
6592 def_node,
6593 kind,
6594 swift_scope_chain(&def_node, source),
6595 true,
6596 );
6597 }
6598 if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
6599 push_captured_symbol(
6600 &mut symbols,
6601 source,
6602 lang,
6603 name_node,
6604 def_node,
6605 SymbolKind::Interface,
6606 swift_scope_chain(&def_node, source),
6607 true,
6608 );
6609 }
6610 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
6611 let scope_chain = swift_scope_chain(&def_node, source);
6612 let kind = if scope_chain.is_empty() {
6613 SymbolKind::Function
6614 } else {
6615 SymbolKind::Method
6616 };
6617 push_captured_symbol(
6618 &mut symbols,
6619 source,
6620 lang,
6621 name_node,
6622 def_node,
6623 kind,
6624 scope_chain,
6625 true,
6626 );
6627 }
6628 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
6629 push_captured_symbol(
6630 &mut symbols,
6631 source,
6632 lang,
6633 name_node,
6634 def_node,
6635 SymbolKind::Variable,
6636 swift_scope_chain(&def_node, source),
6637 true,
6638 );
6639 }
6640 if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
6641 push_captured_symbol(
6642 &mut symbols,
6643 source,
6644 lang,
6645 name_node,
6646 def_node,
6647 SymbolKind::TypeAlias,
6648 swift_scope_chain(&def_node, source),
6649 true,
6650 );
6651 }
6652 }
6653
6654 dedup_symbols(&mut symbols);
6655 Ok(symbols)
6656}
6657
6658fn php_scope_chain(node: &Node, source: &str) -> Vec<String> {
6659 let mut chain = Vec::new();
6660 let mut current = node.parent();
6661
6662 while let Some(parent) = current {
6663 match parent.kind() {
6664 "namespace_definition"
6665 | "class_declaration"
6666 | "interface_declaration"
6667 | "trait_declaration"
6668 | "enum_declaration" => {
6669 if let Some(name_node) = parent.child_by_field_name("name") {
6670 chain.push(node_text(source, &name_node).to_string());
6671 }
6672 }
6673 _ => {}
6674 }
6675 current = parent.parent();
6676 }
6677
6678 chain.reverse();
6679 chain
6680}
6681
6682fn extract_scss_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
6683 let lang = LangId::Scss;
6684 let capture_names = query.capture_names();
6685 let mut symbols = Vec::new();
6686 let mut cursor = QueryCursor::new();
6687 let mut matches = cursor.matches(query, *root, source.as_bytes());
6688
6689 while let Some(m) = {
6690 matches.advance();
6691 matches.get()
6692 } {
6693 let mut mixin_name_node = None;
6694 let mut mixin_def_node = None;
6695 let mut fn_name_node = None;
6696 let mut fn_def_node = None;
6697 let mut var_name_node = None;
6698 let mut var_def_node = None;
6699 let mut selector_name_node = None;
6700 let mut selector_def_node = None;
6701
6702 for cap in m.captures {
6703 let Some(&name) = capture_names.get(cap.index as usize) else {
6704 continue;
6705 };
6706 match name {
6707 "mixin.name" => mixin_name_node = Some(cap.node),
6708 "mixin.def" => mixin_def_node = Some(cap.node),
6709 "fn.name" => fn_name_node = Some(cap.node),
6710 "fn.def" => fn_def_node = Some(cap.node),
6711 "var.name" => var_name_node = Some(cap.node),
6712 "var.def" => var_def_node = Some(cap.node),
6713 "selector.name" => selector_name_node = Some(cap.node),
6714 "selector.def" => selector_def_node = Some(cap.node),
6715 _ => {}
6716 }
6717 }
6718
6719 if let (Some(name_node), Some(def_node)) = (mixin_name_node, mixin_def_node) {
6720 push_captured_symbol(
6721 &mut symbols,
6722 source,
6723 lang,
6724 name_node,
6725 def_node,
6726 SymbolKind::Function,
6727 vec![],
6728 true,
6729 );
6730 }
6731 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
6732 push_captured_symbol(
6733 &mut symbols,
6734 source,
6735 lang,
6736 name_node,
6737 def_node,
6738 SymbolKind::Function,
6739 vec![],
6740 true,
6741 );
6742 }
6743 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
6744 if !node_text(source, &name_node).starts_with('$') {
6745 continue;
6746 }
6747 push_captured_symbol(
6748 &mut symbols,
6749 source,
6750 lang,
6751 name_node,
6752 def_node,
6753 SymbolKind::Variable,
6754 vec![],
6755 true,
6756 );
6757 }
6758 if let (Some(name_node), Some(def_node)) = (selector_name_node, selector_def_node) {
6759 push_captured_symbol(
6760 &mut symbols,
6761 source,
6762 lang,
6763 name_node,
6764 def_node,
6765 SymbolKind::Class,
6766 vec![],
6767 true,
6768 );
6769 }
6770 }
6771
6772 Ok(symbols)
6773}
6774
6775fn extract_php_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
6776 let lang = LangId::Php;
6777 let capture_names = query.capture_names();
6778 let mut symbols = Vec::new();
6779 let mut cursor = QueryCursor::new();
6780 let mut matches = cursor.matches(query, *root, source.as_bytes());
6781
6782 while let Some(m) = {
6783 matches.advance();
6784 matches.get()
6785 } {
6786 let mut namespace_name_node = None;
6787 let mut namespace_def_node = None;
6788 let mut class_name_node = None;
6789 let mut class_def_node = None;
6790 let mut interface_name_node = None;
6791 let mut interface_def_node = None;
6792 let mut trait_name_node = None;
6793 let mut trait_def_node = None;
6794 let mut enum_name_node = None;
6795 let mut enum_def_node = None;
6796 let mut fn_name_node = None;
6797 let mut fn_def_node = None;
6798 let mut var_name_node = None;
6799 let mut var_def_node = None;
6800
6801 for cap in m.captures {
6802 let Some(&name) = capture_names.get(cap.index as usize) else {
6803 continue;
6804 };
6805 match name {
6806 "namespace.name" => namespace_name_node = Some(cap.node),
6807 "namespace.def" => namespace_def_node = Some(cap.node),
6808 "class.name" => class_name_node = Some(cap.node),
6809 "class.def" => class_def_node = Some(cap.node),
6810 "interface.name" => interface_name_node = Some(cap.node),
6811 "interface.def" => interface_def_node = Some(cap.node),
6812 "trait.name" => trait_name_node = Some(cap.node),
6813 "trait.def" => trait_def_node = Some(cap.node),
6814 "enum.name" => enum_name_node = Some(cap.node),
6815 "enum.def" => enum_def_node = Some(cap.node),
6816 "fn.name" => fn_name_node = Some(cap.node),
6817 "fn.def" => fn_def_node = Some(cap.node),
6818 "var.name" => var_name_node = Some(cap.node),
6819 "var.def" => var_def_node = Some(cap.node),
6820 _ => {}
6821 }
6822 }
6823
6824 if let (Some(name_node), Some(def_node)) = (namespace_name_node, namespace_def_node) {
6825 push_captured_symbol(
6826 &mut symbols,
6827 source,
6828 lang,
6829 name_node,
6830 def_node,
6831 SymbolKind::Class,
6832 php_scope_chain(&def_node, source),
6833 true,
6834 );
6835 }
6836 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
6837 push_captured_symbol(
6838 &mut symbols,
6839 source,
6840 lang,
6841 name_node,
6842 def_node,
6843 SymbolKind::Class,
6844 php_scope_chain(&def_node, source),
6845 true,
6846 );
6847 }
6848 if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
6849 push_captured_symbol(
6850 &mut symbols,
6851 source,
6852 lang,
6853 name_node,
6854 def_node,
6855 SymbolKind::Interface,
6856 php_scope_chain(&def_node, source),
6857 true,
6858 );
6859 }
6860 if let (Some(name_node), Some(def_node)) = (trait_name_node, trait_def_node) {
6861 push_captured_symbol(
6862 &mut symbols,
6863 source,
6864 lang,
6865 name_node,
6866 def_node,
6867 SymbolKind::Interface,
6868 php_scope_chain(&def_node, source),
6869 true,
6870 );
6871 }
6872 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
6873 push_captured_symbol(
6874 &mut symbols,
6875 source,
6876 lang,
6877 name_node,
6878 def_node,
6879 SymbolKind::Enum,
6880 php_scope_chain(&def_node, source),
6881 true,
6882 );
6883 }
6884 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
6885 let scope_chain = php_scope_chain(&def_node, source);
6886 let kind = if scope_chain.is_empty() || def_node.kind() == "function_definition" {
6887 SymbolKind::Function
6888 } else {
6889 SymbolKind::Method
6890 };
6891 push_captured_symbol(
6892 &mut symbols,
6893 source,
6894 lang,
6895 name_node,
6896 def_node,
6897 kind,
6898 scope_chain,
6899 true,
6900 );
6901 }
6902 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
6903 push_captured_symbol(
6904 &mut symbols,
6905 source,
6906 lang,
6907 name_node,
6908 def_node,
6909 SymbolKind::Variable,
6910 php_scope_chain(&def_node, source),
6911 true,
6912 );
6913 }
6914 }
6915
6916 dedup_symbols(&mut symbols);
6917 Ok(symbols)
6918}
6919
6920fn lua_scope_chain(node: &Node, source: &str) -> Vec<String> {
6921 let mut chain = Vec::new();
6922
6923 if node.kind() == "function_declaration" {
6924 if let Some(name_node) = node.child_by_field_name("name") {
6925 match name_node.kind() {
6926 "dot_index_expression" | "method_index_expression" => {
6927 if let Some(table_node) = name_node.child_by_field_name("table") {
6928 chain.push(node_text(source, &table_node).to_string());
6929 }
6930 }
6931 _ => {}
6932 }
6933 }
6934 }
6935
6936 chain
6937}
6938
6939fn extract_lua_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
6940 let lang = LangId::Lua;
6941 let capture_names = query.capture_names();
6942 let mut symbols = Vec::new();
6943 let mut cursor = QueryCursor::new();
6944 let mut matches = cursor.matches(query, *root, source.as_bytes());
6945
6946 while let Some(m) = {
6947 matches.advance();
6948 matches.get()
6949 } {
6950 let mut fn_name_node = None;
6951 let mut fn_def_node = None;
6952 let mut var_name_node = None;
6953 let mut var_def_node = None;
6954
6955 for cap in m.captures {
6956 let Some(&name) = capture_names.get(cap.index as usize) else {
6957 continue;
6958 };
6959 match name {
6960 "fn.name" => fn_name_node = Some(cap.node),
6961 "fn.def" => fn_def_node = Some(cap.node),
6962 "var.name" => var_name_node = Some(cap.node),
6963 "var.def" => var_def_node = Some(cap.node),
6964 _ => {}
6965 }
6966 }
6967
6968 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
6969 let scope_chain = lua_scope_chain(&def_node, source);
6970 let kind = if scope_chain.is_empty() {
6971 SymbolKind::Function
6972 } else {
6973 SymbolKind::Method
6974 };
6975 push_captured_symbol(
6976 &mut symbols,
6977 source,
6978 lang,
6979 name_node,
6980 def_node,
6981 kind,
6982 scope_chain,
6983 true,
6984 );
6985 }
6986 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
6987 push_captured_symbol(
6988 &mut symbols,
6989 source,
6990 lang,
6991 name_node,
6992 def_node,
6993 SymbolKind::Variable,
6994 vec![],
6995 true,
6996 );
6997 }
6998 }
6999
7000 dedup_symbols(&mut symbols);
7001 Ok(symbols)
7002}
7003
7004fn perl_package_name(source: &str, node: &Node) -> Option<String> {
7005 let mut cursor = node.walk();
7006 if !cursor.goto_first_child() {
7007 return None;
7008 }
7009
7010 loop {
7011 let child = cursor.node();
7012 if child.kind() == "package" {
7013 return Some(node_text(source, &child).to_string());
7014 }
7015 if !cursor.goto_next_sibling() {
7016 break;
7017 }
7018 }
7019
7020 None
7021}
7022
7023fn perl_scope_chain(node: &Node, source: &str) -> Vec<String> {
7024 let mut chain = Vec::new();
7025 let mut current = node.parent();
7026
7027 while let Some(parent) = current {
7028 if parent.kind() == "package_statement" {
7029 if let Some(name) = perl_package_name(source, &parent) {
7030 chain.push(name);
7031 }
7032 }
7033 current = parent.parent();
7034 }
7035
7036 if chain.is_empty() && node.kind() != "package_statement" {
7037 let mut sibling = node.prev_sibling();
7038 while let Some(prev) = sibling {
7039 if prev.kind() == "package_statement" {
7040 if let Some(name) = perl_package_name(source, &prev) {
7041 chain.push(name);
7042 }
7043 break;
7044 }
7045 sibling = prev.prev_sibling();
7046 }
7047 }
7048
7049 chain.reverse();
7050 chain
7051}
7052
7053fn extract_perl_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
7054 let lang = LangId::Perl;
7055 let capture_names = query.capture_names();
7056 let mut symbols = Vec::new();
7057 let mut cursor = QueryCursor::new();
7058 let mut matches = cursor.matches(query, *root, source.as_bytes());
7059
7060 while let Some(m) = {
7061 matches.advance();
7062 matches.get()
7063 } {
7064 let mut package_name_node = None;
7065 let mut package_def_node = None;
7066 let mut fn_name_node = None;
7067 let mut fn_def_node = None;
7068 let mut var_name_node = None;
7069 let mut var_def_node = None;
7070 let mut const_pragma_node = None;
7071 let mut const_name_node = None;
7072 let mut const_def_node = None;
7073
7074 for cap in m.captures {
7075 let Some(&name) = capture_names.get(cap.index as usize) else {
7076 continue;
7077 };
7078 match name {
7079 "package.name" => package_name_node = Some(cap.node),
7080 "package.def" => package_def_node = Some(cap.node),
7081 "fn.name" => fn_name_node = Some(cap.node),
7082 "fn.def" => fn_def_node = Some(cap.node),
7083 "var.name" => var_name_node = Some(cap.node),
7084 "var.def" => var_def_node = Some(cap.node),
7085 "const.pragma" => const_pragma_node = Some(cap.node),
7086 "const.name" => const_name_node = Some(cap.node),
7087 "const.def" => const_def_node = Some(cap.node),
7088 _ => {}
7089 }
7090 }
7091
7092 if let (Some(pragma_node), Some(name_node), Some(def_node)) =
7097 (const_pragma_node, const_name_node, const_def_node)
7098 {
7099 if node_text(source, &pragma_node) == "constant" {
7100 var_name_node = Some(name_node);
7101 var_def_node = Some(def_node);
7102 }
7103 }
7104
7105 if let (Some(name_node), Some(def_node)) = (package_name_node, package_def_node) {
7106 push_captured_symbol(
7107 &mut symbols,
7108 source,
7109 lang,
7110 name_node,
7111 def_node,
7112 SymbolKind::Class,
7113 vec![],
7114 true,
7115 );
7116 }
7117 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
7118 let scope_chain = perl_scope_chain(&def_node, source);
7119 let kind = if scope_chain.is_empty() {
7120 SymbolKind::Function
7121 } else {
7122 SymbolKind::Method
7123 };
7124 push_captured_symbol(
7125 &mut symbols,
7126 source,
7127 lang,
7128 name_node,
7129 def_node,
7130 kind,
7131 scope_chain,
7132 true,
7133 );
7134 }
7135 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
7136 push_captured_symbol(
7137 &mut symbols,
7138 source,
7139 lang,
7140 name_node,
7141 def_node,
7142 SymbolKind::Variable,
7143 perl_scope_chain(&def_node, source),
7144 true,
7145 );
7146 }
7147 }
7148
7149 dedup_symbols(&mut symbols);
7150 Ok(symbols)
7151}
7152
7153fn extract_vue_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
7154 let mut symbols = Vec::new();
7155 collect_vue_sections(source, root, &mut symbols, true);
7156 dedup_symbols(&mut symbols);
7157 Ok(symbols)
7158}
7159
7160fn collect_vue_sections(
7161 source: &str,
7162 node: &Node,
7163 symbols: &mut Vec<Symbol>,
7164 allow_sections: bool,
7165) {
7166 let mut cursor = node.walk();
7167 if !cursor.goto_first_child() {
7168 return;
7169 }
7170
7171 loop {
7172 let child = cursor.node();
7173 if let Some(section_name) = vue_section_name(&child) {
7174 if allow_sections {
7175 symbols.push(Symbol {
7176 name: section_name.to_string(),
7177 kind: SymbolKind::Heading,
7178 range: node_range(&child),
7179 signature: vue_opening_tag_signature(source, &child),
7180 scope_chain: vec![],
7181 exported: false,
7182 parent: None,
7183 });
7184 }
7185 } else {
7186 collect_vue_sections(
7187 source,
7188 &child,
7189 symbols,
7190 allow_sections && child.kind() == "document",
7191 );
7192 }
7193
7194 if !cursor.goto_next_sibling() {
7195 break;
7196 }
7197 }
7198}
7199
7200fn vue_section_name(node: &Node) -> Option<&'static str> {
7201 match node.kind() {
7202 "template_element" => Some("template"),
7203 "script_element" => Some("script"),
7204 "style_element" => Some("style"),
7205 _ => None,
7206 }
7207}
7208
7209fn vue_opening_tag_signature(source: &str, node: &Node) -> Option<String> {
7210 find_child_by_kind(*node, "start_tag")
7211 .or_else(|| find_child_by_kind(*node, "script_start_tag"))
7212 .or_else(|| find_child_by_kind(*node, "style_start_tag"))
7213 .or_else(|| find_child_by_kind(*node, "template_start_tag"))
7214 .map(|tag| node_text(source, &tag).trim().to_string())
7215}
7216
7217fn source_line_end_col(source: &str, line: u32) -> u32 {
7218 source
7219 .lines()
7220 .nth(line as usize)
7221 .map(|line| line.len() as u32)
7222 .unwrap_or(0)
7223}
7224
7225fn extract_html_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
7226 let mut headings = Vec::new();
7227 collect_html_headings(source, root, &mut headings);
7228
7229 let total_lines = source.lines().count() as u32;
7230
7231 for i in 0..headings.len() {
7235 let level = headings[i].level;
7236 let section_end = headings[i + 1..]
7237 .iter()
7238 .find(|heading| heading.level <= level)
7239 .map(|heading| heading.symbol.range.start_line.saturating_sub(1))
7240 .unwrap_or_else(|| total_lines.saturating_sub(1));
7241 headings[i].symbol.range.end_line = section_end;
7242 if section_end != headings[i].symbol.range.start_line {
7243 headings[i].symbol.range.end_col = source_line_end_col(source, section_end);
7244 }
7245 }
7246
7247 let mut scope_stack: Vec<(u8, String)> = Vec::new(); for heading in &mut headings {
7250 while scope_stack
7252 .last()
7253 .is_some_and(|(level, _)| *level >= heading.level)
7254 {
7255 scope_stack.pop();
7256 }
7257 heading.symbol.scope_chain = scope_stack.iter().map(|(_, name)| name.clone()).collect();
7258 heading.symbol.parent = scope_stack.last().map(|(_, name)| name.clone());
7259 scope_stack.push((heading.level, heading.symbol.name.clone()));
7260 }
7261
7262 Ok(headings.into_iter().map(|heading| heading.symbol).collect())
7263}
7264
7265struct HtmlHeading {
7267 level: u8,
7268 symbol: Symbol,
7269 anchor_id: Option<String>,
7270}
7271
7272fn collect_html_headings(source: &str, node: &Node, headings: &mut Vec<HtmlHeading>) {
7274 let mut cursor = node.walk();
7275 if !cursor.goto_first_child() {
7276 return;
7277 }
7278
7279 loop {
7280 let child = cursor.node();
7281 if child.kind() == "element" {
7282 if let Some(start_tag) = child
7284 .child_by_field_name("start_tag")
7285 .or_else(|| child.child(0).filter(|c| c.kind() == "start_tag"))
7286 {
7287 if let Some(tag_name_node) = start_tag
7288 .child_by_field_name("tag_name")
7289 .or_else(|| start_tag.child(1).filter(|c| c.kind() == "tag_name"))
7290 {
7291 let tag_name = node_text(source, &tag_name_node).to_lowercase();
7292 if let Some(level) = match tag_name.as_str() {
7293 "h1" => Some(1u8),
7294 "h2" => Some(2),
7295 "h3" => Some(3),
7296 "h4" => Some(4),
7297 "h5" => Some(5),
7298 "h6" => Some(6),
7299 _ => None,
7300 } {
7301 let text = extract_element_text(source, &child).trim().to_string();
7303 if !text.is_empty() {
7304 let range = node_range(&child);
7305 let signature = format!("<h{}> {}", level, text);
7306 headings.push(HtmlHeading {
7307 level,
7308 anchor_id: html_heading_anchor_id(source, &start_tag),
7309 symbol: Symbol {
7310 name: text,
7311 kind: SymbolKind::Heading,
7312 range,
7313 signature: Some(signature),
7314 scope_chain: vec![], exported: false,
7316 parent: None, },
7318 });
7319 }
7320 }
7321 }
7322 }
7323 collect_html_headings(source, &child, headings);
7325 } else {
7326 collect_html_headings(source, &child, headings);
7328 }
7329
7330 if !cursor.goto_next_sibling() {
7331 break;
7332 }
7333 }
7334}
7335
7336fn extract_html_heading_anchors(source: &str, root: &Node) -> Vec<crate::language::HeadingAnchor> {
7337 let mut headings = Vec::new();
7338 collect_html_headings(source, root, &mut headings);
7339 headings
7340 .into_iter()
7341 .filter_map(|heading| {
7342 heading.anchor_id.map(|id| crate::language::HeadingAnchor {
7343 start_line: heading.symbol.range.start_line,
7344 start_col: heading.symbol.range.start_col,
7345 id,
7346 })
7347 })
7348 .collect()
7349}
7350
7351fn html_heading_anchor_id(source: &str, start_tag: &Node) -> Option<String> {
7352 for expected_name in ["id", "name"] {
7353 let mut cursor = start_tag.walk();
7354 for attribute in start_tag.named_children(&mut cursor) {
7355 if attribute.kind() != "attribute" {
7356 continue;
7357 }
7358 let Some(name_node) = attribute
7359 .child_by_field_name("name")
7360 .or_else(|| find_child_by_kind(attribute, "attribute_name"))
7361 else {
7362 continue;
7363 };
7364 if !node_text(source, &name_node).eq_ignore_ascii_case(expected_name) {
7365 continue;
7366 }
7367 let Some(value_node) = attribute
7368 .child_by_field_name("value")
7369 .or_else(|| find_child_by_kind(attribute, "quoted_attribute_value"))
7370 .or_else(|| find_child_by_kind(attribute, "attribute_value"))
7371 else {
7372 continue;
7373 };
7374 let raw_value = node_text(source, &value_node).trim();
7375 let value = raw_value
7376 .strip_prefix('"')
7377 .and_then(|value| value.strip_suffix('"'))
7378 .or_else(|| {
7379 raw_value
7380 .strip_prefix('\'')
7381 .and_then(|value| value.strip_suffix('\''))
7382 })
7383 .unwrap_or(raw_value)
7384 .trim();
7385 if !value.is_empty() {
7386 return Some(value.to_string());
7387 }
7388 }
7389 }
7390 None
7391}
7392
7393fn extract_element_text(source: &str, node: &Node) -> String {
7395 let mut text = String::new();
7396 let mut cursor = node.walk();
7397 if !cursor.goto_first_child() {
7398 return text;
7399 }
7400 loop {
7401 let child = cursor.node();
7402 match child.kind() {
7403 "text" => {
7404 text.push_str(node_text(source, &child));
7405 }
7406 "element" => {
7407 text.push_str(&extract_element_text(source, &child));
7409 }
7410 _ => {}
7411 }
7412 if !cursor.goto_next_sibling() {
7413 break;
7414 }
7415 }
7416 text
7417}
7418
7419struct RawHeading {
7420 name: String,
7421 level: u8,
7422 range: Range,
7423}
7424
7425fn parse_atx_heading(source: &str, node: &Node) -> Option<(String, u8)> {
7426 let mut heading_level = 1;
7427 let mut heading_name = String::new();
7428 let mut cursor = node.walk();
7429 if cursor.goto_first_child() {
7430 loop {
7431 let child = cursor.node();
7432 let kind = child.kind();
7433 if kind.starts_with("atx_h") && kind.ends_with("_marker") {
7434 heading_level = kind
7435 .strip_prefix("atx_h")
7436 .and_then(|s| s.strip_suffix("_marker"))
7437 .and_then(|s| s.parse::<u8>().ok())
7438 .unwrap_or(1);
7439 } else if kind == "inline" {
7440 heading_name = node_text(source, &child).trim().to_string();
7441 }
7442 if !cursor.goto_next_sibling() {
7443 break;
7444 }
7445 }
7446 }
7447 if heading_name.is_empty() {
7448 None
7449 } else {
7450 Some((heading_name, heading_level))
7451 }
7452}
7453
7454fn parse_setext_heading(source: &str, node: &Node) -> Option<(String, u8)> {
7455 let mut heading_level = 1;
7456 let mut heading_name = String::new();
7457 let mut cursor = node.walk();
7458 if cursor.goto_first_child() {
7459 loop {
7460 let child = cursor.node();
7461 let kind = child.kind();
7462 if kind.starts_with("setext_h") && kind.ends_with("_underline") {
7463 heading_level = kind
7464 .strip_prefix("setext_h")
7465 .and_then(|s| s.strip_suffix("_underline"))
7466 .and_then(|s| s.parse::<u8>().ok())
7467 .unwrap_or(1);
7468 } else if kind == "paragraph" {
7469 heading_name = node_text(source, &child).trim().to_string();
7470 }
7471 if !cursor.goto_next_sibling() {
7472 break;
7473 }
7474 }
7475 }
7476 if heading_name.is_empty() {
7477 None
7478 } else {
7479 Some((heading_name, heading_level))
7480 }
7481}
7482
7483fn collect_headings(source: &str, node: &Node, headings: &mut Vec<RawHeading>) {
7484 let kind = node.kind();
7485 if kind == "atx_heading" {
7486 if let Some((name, level)) = parse_atx_heading(source, node) {
7487 headings.push(RawHeading {
7488 name,
7489 level,
7490 range: node_range(node),
7491 });
7492 }
7493 } else if kind == "setext_heading" {
7494 if let Some((name, level)) = parse_setext_heading(source, node) {
7495 headings.push(RawHeading {
7496 name,
7497 level,
7498 range: node_range(node),
7499 });
7500 }
7501 } else {
7502 let mut cursor = node.walk();
7503 if cursor.goto_first_child() {
7504 loop {
7505 collect_headings(source, &cursor.node(), headings);
7506 if !cursor.goto_next_sibling() {
7507 break;
7508 }
7509 }
7510 }
7511 }
7512}
7513
7514fn extract_md_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
7518 let mut raw_headings = Vec::new();
7519 collect_headings(source, root, &mut raw_headings);
7520
7521 let mut symbols = Vec::new();
7522 let total_lines = source.lines().count() as u32;
7523
7524 let mut active_headings: Vec<Option<String>> = vec![None; 7];
7525
7526 for i in 0..raw_headings.len() {
7527 let h = &raw_headings[i];
7528 let level = h.level as usize;
7529
7530 let mut scope_chain = Vec::new();
7532 for l in 1..level {
7533 if let Some(ref name) = active_headings[l] {
7534 scope_chain.push(name.clone());
7535 }
7536 }
7537
7538 active_headings[level] = Some(h.name.clone());
7540 for l in (level + 1)..7 {
7541 active_headings[l] = None;
7542 }
7543
7544 let start_line = h.range.start_line;
7546 let start_col = h.range.start_col;
7547
7548 let mut end_line = total_lines.saturating_sub(1);
7550 let mut end_col = if total_lines > 0 {
7551 source_line_end_col(source, end_line)
7552 } else {
7553 0
7554 };
7555
7556 for j in (i + 1)..raw_headings.len() {
7557 if raw_headings[j].level <= h.level {
7558 let next_start_line = raw_headings[j].range.start_line;
7560 end_line = next_start_line.saturating_sub(1).max(start_line);
7561 end_col = source_line_end_col(source, end_line);
7562 break;
7563 }
7564 }
7565
7566 let signature = format!("{} {}", "#".repeat((h.level as usize).min(6)), h.name);
7567
7568 symbols.push(Symbol {
7569 name: h.name.clone(),
7570 kind: SymbolKind::Heading,
7571 range: Range {
7572 start_line,
7573 start_col,
7574 end_line,
7575 end_col,
7576 },
7577 signature: Some(signature),
7578 scope_chain: scope_chain.clone(),
7579 exported: false,
7580 parent: scope_chain.last().cloned(),
7581 });
7582 }
7583
7584 Ok(symbols)
7585}
7586
7587fn dedup_symbols(symbols: &mut Vec<Symbol>) {
7591 let mut seen = std::collections::HashSet::new();
7592 symbols.retain(|s| {
7593 let key = (s.name.clone(), format!("{:?}", s.kind), s.range.start_line);
7594 seen.insert(key)
7595 });
7596}
7597
7598pub struct TreeSitterProvider {
7601 symbol_cache: SharedSymbolCache,
7602}
7603
7604#[derive(Debug, Clone)]
7605struct ReExportTarget {
7606 file: PathBuf,
7607 symbol_name: String,
7608}
7609
7610impl TreeSitterProvider {
7611 pub fn new() -> Self {
7613 Self::with_symbol_cache(Arc::new(RwLock::new(SymbolCache::new())))
7614 }
7615
7616 pub fn with_symbol_cache(symbol_cache: SharedSymbolCache) -> Self {
7618 Self { symbol_cache }
7619 }
7620
7621 pub fn symbol_cache_len(&self) -> usize {
7623 self.symbol_cache
7624 .read()
7625 .map(|cache| cache.len())
7626 .unwrap_or(0)
7627 }
7628
7629 pub fn symbol_cache(&self) -> SharedSymbolCache {
7631 Arc::clone(&self.symbol_cache)
7632 }
7633
7634 fn resolve_symbol_inner(
7635 &self,
7636 parser: &mut FileParser,
7637 file: &Path,
7638 name: &str,
7639 depth: usize,
7640 visited: &mut HashSet<(PathBuf, String)>,
7641 ) -> Result<Vec<SymbolMatch>, AftError> {
7642 if depth > MAX_REEXPORT_DEPTH {
7643 return Ok(Vec::new());
7644 }
7645
7646 let canonical_file = std::fs::canonicalize(file).unwrap_or_else(|_| file.to_path_buf());
7647 if !visited.insert((canonical_file, name.to_string())) {
7648 return Ok(Vec::new());
7649 }
7650
7651 let symbols = parser.extract_symbols(file)?;
7652 let local_matches = symbol_matches_in_file(file, &symbols, name);
7653 if !local_matches.is_empty() {
7654 return Ok(local_matches);
7655 }
7656
7657 if name == "default" {
7658 let default_matches = self.resolve_local_default_export(parser, file, &symbols)?;
7659 if !default_matches.is_empty() {
7660 return Ok(default_matches);
7661 }
7662 }
7663
7664 let reexport_targets = self.collect_reexport_targets(parser, file, name)?;
7665 let mut matches = Vec::new();
7666 let mut seen = HashSet::new();
7667 for target in reexport_targets {
7668 for resolved in self.resolve_symbol_inner(
7669 parser,
7670 &target.file,
7671 &target.symbol_name,
7672 depth + 1,
7673 visited,
7674 )? {
7675 let key = format!(
7676 "{}:{}:{}:{}:{}:{}",
7677 resolved.file,
7678 resolved.symbol.name,
7679 resolved.symbol.range.start_line,
7680 resolved.symbol.range.start_col,
7681 resolved.symbol.range.end_line,
7682 resolved.symbol.range.end_col
7683 );
7684 if seen.insert(key) {
7685 matches.push(resolved);
7686 }
7687 }
7688 }
7689
7690 Ok(matches)
7691 }
7692
7693 fn collect_reexport_targets(
7694 &self,
7695 parser: &mut FileParser,
7696 file: &Path,
7697 requested_name: &str,
7698 ) -> Result<Vec<ReExportTarget>, AftError> {
7699 let (source, tree, lang) = self.read_parsed_file(parser, file)?;
7700 if !matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
7701 return Ok(Vec::new());
7702 }
7703
7704 let mut targets = Vec::new();
7705 let root = tree.root_node();
7706 let from_dir = file.parent().unwrap_or_else(|| Path::new("."));
7707
7708 let mut cursor = root.walk();
7709 if !cursor.goto_first_child() {
7710 return Ok(targets);
7711 }
7712
7713 loop {
7714 let node = cursor.node();
7715 if node.kind() == "export_statement" {
7716 let Some(source_node) = node.child_by_field_name("source") else {
7717 if let Some(export_clause) = find_child_by_kind(node, "export_clause") {
7718 if let Some(symbol_name) =
7719 resolve_export_clause_name(&source, &export_clause, requested_name)
7720 {
7721 targets.push(ReExportTarget {
7722 file: file.to_path_buf(),
7723 symbol_name,
7724 });
7725 }
7726 }
7727 if !cursor.goto_next_sibling() {
7728 break;
7729 }
7730 continue;
7731 };
7732
7733 let Some(module_path) = string_content(&source, &source_node) else {
7734 if !cursor.goto_next_sibling() {
7735 break;
7736 }
7737 continue;
7738 };
7739
7740 let Some(target_file) = resolve_module_path(from_dir, &module_path) else {
7741 if !cursor.goto_next_sibling() {
7742 break;
7743 }
7744 continue;
7745 };
7746
7747 if let Some(export_clause) = find_child_by_kind(node, "export_clause") {
7748 if let Some(symbol_name) =
7749 resolve_export_clause_name(&source, &export_clause, requested_name)
7750 {
7751 targets.push(ReExportTarget {
7752 file: target_file,
7753 symbol_name,
7754 });
7755 }
7756 } else if export_statement_has_wildcard(&source, &node) {
7757 targets.push(ReExportTarget {
7758 file: target_file,
7759 symbol_name: requested_name.to_string(),
7760 });
7761 }
7762 }
7763
7764 if !cursor.goto_next_sibling() {
7765 break;
7766 }
7767 }
7768
7769 Ok(targets)
7770 }
7771
7772 fn resolve_local_default_export(
7773 &self,
7774 parser: &mut FileParser,
7775 file: &Path,
7776 symbols: &[Symbol],
7777 ) -> Result<Vec<SymbolMatch>, AftError> {
7778 let (source, tree, lang) = self.read_parsed_file(parser, file)?;
7779 if !matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
7780 return Ok(Vec::new());
7781 }
7782
7783 let root = tree.root_node();
7784 let mut matches = Vec::new();
7785 let mut seen = HashSet::new();
7786
7787 let mut cursor = root.walk();
7788 if !cursor.goto_first_child() {
7789 return Ok(matches);
7790 }
7791
7792 loop {
7793 let node = cursor.node();
7794 if node.kind() == "export_statement"
7795 && node.child_by_field_name("source").is_none()
7796 && node_contains_token(&source, &node, "default")
7797 {
7798 if let Some(target_name) = default_export_target_name(&source, &node) {
7799 for symbol_match in symbol_matches_in_file(file, symbols, &target_name) {
7800 let key = format!(
7801 "{}:{}:{}:{}:{}:{}",
7802 symbol_match.file,
7803 symbol_match.symbol.name,
7804 symbol_match.symbol.range.start_line,
7805 symbol_match.symbol.range.start_col,
7806 symbol_match.symbol.range.end_line,
7807 symbol_match.symbol.range.end_col
7808 );
7809 if seen.insert(key) {
7810 matches.push(symbol_match);
7811 }
7812 }
7813 }
7814 }
7815
7816 if !cursor.goto_next_sibling() {
7817 break;
7818 }
7819 }
7820
7821 Ok(matches)
7822 }
7823
7824 fn read_parsed_file(
7825 &self,
7826 parser: &mut FileParser,
7827 file: &Path,
7828 ) -> Result<(String, Tree, LangId), AftError> {
7829 let current_mtime = std::fs::metadata(file)
7830 .and_then(|m| m.modified())
7831 .map_err(|e| AftError::FileNotFound {
7832 path: format!("{}: {}", file.display(), e),
7833 })?;
7834 let source = std::fs::read_to_string(file).map_err(|e| AftError::FileNotFound {
7835 path: format!("{}: {}", file.display(), e),
7836 })?;
7837 let size = source.len() as u64;
7838 let content_hash = content_hash_for_source(&source);
7839 let (tree, lang) =
7840 parser.parse_with_source(file, &source, current_mtime, size, content_hash)?;
7841 Ok((source, tree.clone(), lang))
7842 }
7843}
7844
7845fn symbol_matches_in_file(file: &Path, symbols: &[Symbol], name: &str) -> Vec<SymbolMatch> {
7846 symbols
7847 .iter()
7848 .filter(|symbol| symbol_query_matches(symbol, name))
7849 .cloned()
7850 .map(|symbol| SymbolMatch {
7851 file: file.display().to_string(),
7852 symbol,
7853 })
7854 .collect()
7855}
7856
7857fn symbol_query_matches(symbol: &Symbol, query: &str) -> bool {
7858 if symbol.name == query {
7859 return true;
7860 }
7861
7862 if !query.contains('.') && !query.contains("::") {
7863 return false;
7864 }
7865
7866 let mut parts = symbol
7867 .scope_chain
7868 .iter()
7869 .filter(|part| !part.is_empty())
7870 .map(String::as_str)
7871 .collect::<Vec<_>>();
7872 if parts.is_empty() {
7873 return false;
7874 }
7875 parts.push(symbol.name.as_str());
7876
7877 parts.join(".") == query || parts.join("::") == query
7878}
7879
7880fn string_content(source: &str, node: &Node) -> Option<String> {
7881 let text = node_text(source, node);
7882 if text.len() < 2 {
7883 return None;
7884 }
7885
7886 Some(
7887 text.trim_start_matches(|c| c == '\'' || c == '"')
7888 .trim_end_matches(|c| c == '\'' || c == '"')
7889 .to_string(),
7890 )
7891}
7892
7893fn find_child_by_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
7894 let mut cursor = node.walk();
7895 if !cursor.goto_first_child() {
7896 return None;
7897 }
7898
7899 loop {
7900 let child = cursor.node();
7901 if child.kind() == kind {
7902 return Some(child);
7903 }
7904 if !cursor.goto_next_sibling() {
7905 break;
7906 }
7907 }
7908
7909 None
7910}
7911
7912fn resolve_export_clause_name(
7913 source: &str,
7914 export_clause: &Node,
7915 requested_name: &str,
7916) -> Option<String> {
7917 let mut cursor = export_clause.walk();
7918 if !cursor.goto_first_child() {
7919 return None;
7920 }
7921
7922 loop {
7923 let child = cursor.node();
7924 if child.kind() == "export_specifier" {
7925 let (source_name, exported_name) = export_specifier_names(source, &child)?;
7926 if exported_name == requested_name {
7927 return Some(source_name);
7928 }
7929 }
7930
7931 if !cursor.goto_next_sibling() {
7932 break;
7933 }
7934 }
7935
7936 None
7937}
7938
7939fn export_specifier_names(source: &str, specifier: &Node) -> Option<(String, String)> {
7940 let source_name = specifier
7941 .child_by_field_name("name")
7942 .map(|node| node_text(source, &node).to_string());
7943 let alias_name = specifier
7944 .child_by_field_name("alias")
7945 .map(|node| node_text(source, &node).to_string());
7946
7947 if let Some(source_name) = source_name {
7948 let exported_name = alias_name.unwrap_or_else(|| source_name.clone());
7949 return Some((source_name, exported_name));
7950 }
7951
7952 let mut names = Vec::new();
7953 let mut cursor = specifier.walk();
7954 if cursor.goto_first_child() {
7955 loop {
7956 let child = cursor.node();
7957 let child_text = node_text(source, &child).trim();
7958 if matches!(
7959 child.kind(),
7960 "identifier" | "type_identifier" | "property_identifier"
7961 ) || child_text == "default"
7962 {
7963 names.push(child_text.to_string());
7964 }
7965 if !cursor.goto_next_sibling() {
7966 break;
7967 }
7968 }
7969 }
7970
7971 match names.as_slice() {
7972 [name] => Some((name.clone(), name.clone())),
7973 [source_name, exported_name, ..] => Some((source_name.clone(), exported_name.clone())),
7974 _ => None,
7975 }
7976}
7977
7978fn export_statement_has_wildcard(source: &str, node: &Node) -> bool {
7979 let mut cursor = node.walk();
7980 if !cursor.goto_first_child() {
7981 return false;
7982 }
7983
7984 loop {
7985 if node_text(source, &cursor.node()).trim() == "*" {
7986 return true;
7987 }
7988 if !cursor.goto_next_sibling() {
7989 break;
7990 }
7991 }
7992
7993 false
7994}
7995
7996fn node_contains_token(source: &str, node: &Node, token: &str) -> bool {
7997 let mut cursor = node.walk();
7998 if !cursor.goto_first_child() {
7999 return false;
8000 }
8001
8002 loop {
8003 if node_text(source, &cursor.node()).trim() == token {
8004 return true;
8005 }
8006 if !cursor.goto_next_sibling() {
8007 break;
8008 }
8009 }
8010
8011 false
8012}
8013
8014fn default_export_target_name(source: &str, export_stmt: &Node) -> Option<String> {
8015 if let Some(value_node) = export_stmt.child_by_field_name("value") {
8016 if let Some(name) = default_export_node_name(source, &value_node) {
8017 return Some(name);
8018 }
8019 }
8020
8021 if let Some(declaration_node) = export_stmt.child_by_field_name("declaration") {
8022 if let Some(name) = default_export_node_name(source, &declaration_node) {
8023 return Some(name);
8024 }
8025 }
8026
8027 let mut cursor = export_stmt.walk();
8028 if !cursor.goto_first_child() {
8029 return None;
8030 }
8031
8032 loop {
8033 let child = cursor.node();
8034 if let Some(name) = default_export_node_name(source, &child) {
8035 return Some(name);
8036 }
8037
8038 if !cursor.goto_next_sibling() {
8039 break;
8040 }
8041 }
8042
8043 None
8044}
8045
8046fn default_export_node_name(source: &str, node: &Node) -> Option<String> {
8047 match node.kind() {
8048 "function_declaration"
8049 | "generator_function_declaration"
8050 | "function_expression"
8051 | "generator_function"
8052 | "class_declaration"
8053 | "class" => node
8054 .child_by_field_name("name")
8055 .map(|name_node| node_text(source, &name_node).to_string())
8056 .or_else(|| Some("default".to_string())),
8057 "interface_declaration" | "enum_declaration" | "type_alias_declaration" => node
8058 .child_by_field_name("name")
8059 .map(|name_node| node_text(source, &name_node).to_string()),
8060 "lexical_declaration" => lexical_declaration_name(source, node),
8061 "identifier" | "type_identifier" => {
8062 let text = node_text(source, node);
8063 (text != "export" && text != "default").then(|| text.to_string())
8064 }
8065 _ => None,
8066 }
8067}
8068
8069fn lexical_declaration_name(source: &str, node: &Node) -> Option<String> {
8070 let mut cursor = node.walk();
8071 if !cursor.goto_first_child() {
8072 return None;
8073 }
8074
8075 loop {
8076 let child = cursor.node();
8077 if child.kind() == "variable_declarator" {
8078 if let Some(name_node) = child.child_by_field_name("name") {
8079 return Some(node_text(source, &name_node).to_string());
8080 }
8081 }
8082 if !cursor.goto_next_sibling() {
8083 break;
8084 }
8085 }
8086
8087 None
8088}
8089
8090impl crate::language::LanguageProvider for TreeSitterProvider {
8091 fn resolve_symbol(&self, file: &Path, name: &str) -> Result<Vec<SymbolMatch>, AftError> {
8092 let mut parser = FileParser::with_symbol_cache(self.symbol_cache());
8093 let matches = self.resolve_symbol_inner(&mut parser, file, name, 0, &mut HashSet::new())?;
8094
8095 if matches.is_empty() {
8096 Err(AftError::SymbolNotFound {
8097 name: name.to_string(),
8098 file: file.display().to_string(),
8099 })
8100 } else {
8101 Ok(matches)
8102 }
8103 }
8104
8105 fn list_symbols(&self, file: &Path) -> Result<Vec<Symbol>, AftError> {
8106 let mut parser = FileParser::with_symbol_cache(self.symbol_cache());
8107 parser.extract_symbols(file)
8108 }
8109
8110 fn heading_anchors(
8111 &self,
8112 file: &Path,
8113 ) -> Result<Vec<crate::language::HeadingAnchor>, AftError> {
8114 if detect_language(file) != Some(LangId::Html) {
8115 return Ok(Vec::new());
8116 }
8117 let mut parser = FileParser::with_symbol_cache(self.symbol_cache());
8118 let (source, tree, _) = self.read_parsed_file(&mut parser, file)?;
8119 Ok(extract_html_heading_anchors(&source, &tree.root_node()))
8120 }
8121
8122 fn as_any(&self) -> &dyn std::any::Any {
8123 self
8124 }
8125}
8126
8127#[cfg(test)]
8128#[cfg_attr(not(debug_assertions), allow(dead_code))]
8131mod tests {
8132 use super::*;
8133 use crate::language::LanguageProvider;
8134 use crate::symbol_cache_disk;
8135 use std::path::{Path, PathBuf};
8136
8137 fn fixture_path(name: &str) -> PathBuf {
8138 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
8139 .join("tests")
8140 .join("fixtures")
8141 .join(name)
8142 }
8143
8144 #[test]
8150 fn export_walk_survives_deep_trees_on_a_small_stack() {
8151 const DEPTH: usize = 3000;
8152 let mut source = String::from("const deep = ");
8156 source.push_str(&"(".repeat(DEPTH));
8157 source.push('1');
8158 source.push_str(&")".repeat(DEPTH));
8159 source.push_str(";\nfunction shallow() {}\nexport { deep, shallow };\n");
8160
8161 let exported = std::thread::Builder::new()
8162 .stack_size(256 * 1024)
8163 .spawn(move || {
8164 let grammar = grammar_for(LangId::TypeScript);
8165 let mut parser = Parser::new();
8166 parser.set_language(&grammar).unwrap();
8167 let tree = parser.parse(&source, None).unwrap();
8168 collect_exported_symbol_names(&source, &tree.root_node())
8169 })
8170 .unwrap()
8171 .join()
8172 .expect("export walk must not overflow a 256 KiB stack");
8173
8174 assert!(exported.contains("deep"), "exports: {exported:?}");
8175 assert!(exported.contains("shallow"), "exports: {exported:?}");
8176 }
8177
8178 #[test]
8179 #[ignore = "manual single-file parser/query phase benchmark"]
8180 fn profile_rust_parser_and_query_phases() {
8181 let source = "pub fn tiny() -> bool {\n true\n}\n";
8182
8183 let grammar_started = std::time::Instant::now();
8184 let grammar = grammar_for(LangId::Rust);
8185 let grammar_elapsed = grammar_started.elapsed();
8186
8187 let parser_init_started = std::time::Instant::now();
8188 let mut parser = Parser::new();
8189 parser.set_language(&grammar).unwrap();
8190 let parser_init_elapsed = parser_init_started.elapsed();
8191
8192 let parse_started = std::time::Instant::now();
8193 let tree = parser.parse(source, None).unwrap();
8194 let parse_elapsed = parse_started.elapsed();
8195
8196 let query_compile_started = std::time::Instant::now();
8197 let query = Query::new(&grammar, RS_QUERY).unwrap();
8198 let query_compile_elapsed = query_compile_started.elapsed();
8199
8200 let query_execute_started = std::time::Instant::now();
8201 let mut cursor = QueryCursor::new();
8202 let mut matches = cursor.matches(&query, tree.root_node(), source.as_bytes());
8203 let mut query_matches = 0;
8204 while matches.next().is_some() {
8205 query_matches += 1;
8206 }
8207 let query_execute_elapsed = query_execute_started.elapsed();
8208
8209 let direct_extract_started = std::time::Instant::now();
8210 let symbols = extract_rs_symbols(source, &tree.root_node()).unwrap();
8211 let direct_extract_elapsed = direct_extract_started.elapsed();
8212
8213 eprintln!(
8214 "rust parser/query phases: grammar={grammar_elapsed:?} parser_init={parser_init_elapsed:?} parse_execute={parse_elapsed:?} query_compile={query_compile_elapsed:?} query_execute={query_execute_elapsed:?} direct_extract={direct_extract_elapsed:?} symbols={}",
8215 symbols.len()
8216 );
8217 assert!(query_matches > 0);
8218 assert_eq!(symbols.len(), 1);
8219 }
8220
8221 fn extract_rs_symbols_with_query_oracle(source: &str, root: Node<'_>) -> Vec<Symbol> {
8222 let grammar = grammar_for(LangId::Rust);
8223 let query = Query::new(&grammar, RS_QUERY).expect("compile Rust parity query");
8224 let capture_names = query.capture_names();
8225 let is_pub = |node: &Node<'_>| {
8226 let mut cursor = node.walk();
8227 if cursor.goto_first_child() {
8228 loop {
8229 if cursor.node().kind() == "visibility_modifier" {
8230 return true;
8231 }
8232 if !cursor.goto_next_sibling() {
8233 break;
8234 }
8235 }
8236 }
8237 false
8238 };
8239
8240 let mut symbols = Vec::new();
8241 let mut cursor = QueryCursor::new();
8242 let mut matches = cursor.matches(&query, root, source.as_bytes());
8243 while let Some(query_match) = {
8244 matches.advance();
8245 matches.get()
8246 } {
8247 let mut function_name = None;
8248 let mut function_definition = None;
8249 let mut item_name = None;
8250 let mut item_definition = None;
8251 let mut item_kind = None;
8252 let mut impl_node = None;
8253 for capture in query_match.captures {
8254 let Some(name) = capture_names.get(capture.index as usize) else {
8255 continue;
8256 };
8257 match *name {
8258 "fn.name" => function_name = Some(capture.node),
8259 "fn.def" => function_definition = Some(capture.node),
8260 "struct.name" => {
8261 item_name = Some(capture.node);
8262 item_kind = Some(SymbolKind::Struct);
8263 }
8264 "struct.def" => item_definition = Some(capture.node),
8265 "enum.name" => {
8266 item_name = Some(capture.node);
8267 item_kind = Some(SymbolKind::Enum);
8268 }
8269 "enum.def" => item_definition = Some(capture.node),
8270 "trait.name" => {
8271 item_name = Some(capture.node);
8272 item_kind = Some(SymbolKind::Interface);
8273 }
8274 "trait.def" => item_definition = Some(capture.node),
8275 "impl.def" => impl_node = Some(capture.node),
8276 _ => {}
8277 }
8278 }
8279
8280 if let (Some(name_node), Some(definition)) = (function_name, function_definition) {
8281 let owner = rust_function_declaration_list_owner(&definition);
8282 if owner
8283 .as_ref()
8284 .is_none_or(|owner| owner.kind() == "mod_item")
8285 {
8286 let scope_chain = rust_mod_scope_chain(&definition, source);
8287 symbols.push(Symbol {
8288 name: node_text(source, &name_node).to_string(),
8289 kind: SymbolKind::Function,
8290 range: node_range_with_decorators(&definition, source, LangId::Rust),
8291 signature: Some(extract_signature(source, &definition)),
8292 scope_chain: scope_chain.clone(),
8293 exported: is_pub(&definition),
8294 parent: scope_chain.last().cloned(),
8295 });
8296 }
8297 }
8298
8299 if let (Some(name_node), Some(definition), Some(kind)) =
8300 (item_name, item_definition, item_kind)
8301 {
8302 symbols.push(Symbol {
8303 name: node_text(source, &name_node).to_string(),
8304 kind,
8305 range: node_range_with_decorators(&definition, source, LangId::Rust),
8306 signature: Some(extract_signature(source, &definition)),
8307 scope_chain: Vec::new(),
8308 exported: is_pub(&definition),
8309 parent: None,
8310 });
8311 }
8312
8313 if let Some(impl_node) = impl_node {
8314 let scope_name = rust_impl_scope_name(&impl_node, source);
8315 let parent_name = scope_name
8316 .rsplit(" for ")
8317 .next()
8318 .unwrap_or_default()
8319 .to_string();
8320 let mut impl_cursor = impl_node.walk();
8321 if impl_cursor.goto_first_child() {
8322 loop {
8323 let child = impl_cursor.node();
8324 if child.kind() == "declaration_list" {
8325 let mut method_cursor = child.walk();
8326 if method_cursor.goto_first_child() {
8327 loop {
8328 let method = method_cursor.node();
8329 if method.kind() == "function_item" {
8330 if let Some(name_node) = method.child_by_field_name("name")
8331 {
8332 symbols.push(Symbol {
8333 name: node_text(source, &name_node).to_string(),
8334 kind: SymbolKind::Method,
8335 range: node_range_with_decorators(
8336 &method,
8337 source,
8338 LangId::Rust,
8339 ),
8340 signature: Some(extract_signature(source, &method)),
8341 scope_chain: (!scope_name.is_empty())
8342 .then(|| vec![scope_name.clone()])
8343 .unwrap_or_default(),
8344 exported: is_pub(&method),
8345 parent: (!parent_name.is_empty())
8346 .then(|| parent_name.clone()),
8347 });
8348 }
8349 }
8350 if !method_cursor.goto_next_sibling() {
8351 break;
8352 }
8353 }
8354 }
8355 }
8356 if !impl_cursor.goto_next_sibling() {
8357 break;
8358 }
8359 }
8360 }
8361 }
8362 }
8363
8364 dedup_symbols(&mut symbols);
8365 symbols
8366 }
8367
8368 fn rust_symbol_fingerprint(symbols: &[Symbol]) -> Vec<String> {
8369 symbols
8370 .iter()
8371 .map(|symbol| {
8372 format!(
8373 "{}|{:?}|{:?}|{:?}|{:?}|{}|{:?}",
8374 symbol.name,
8375 symbol.kind,
8376 symbol.range,
8377 symbol.signature,
8378 symbol.scope_chain,
8379 symbol.exported,
8380 symbol.parent
8381 )
8382 })
8383 .collect()
8384 }
8385
8386 #[test]
8387 fn rust_walk_matches_query_oracle_for_adversarial_shapes() {
8388 let source = r#"
8389#[cfg(any())]
8390pub(crate) mod outer {
8391 pub mod inner {
8392 #[doc = "keeps decorator range"]
8393 pub(crate) fn nested<T>() {}
8394
8395 pub(crate) struct Generic<T>(T);
8396 pub enum State { Ready }
8397 pub trait Service<T> {
8398 fn declaration(&self);
8399 }
8400
8401 impl<T> Service<T> for Generic<T> {
8402 #[inline]
8403 pub(crate) fn attributed_method(&self) {}
8404 fn private_method(&self) {}
8405 }
8406 }
8407}
8408"#;
8409 let grammar = grammar_for(LangId::Rust);
8410 let mut parser = Parser::new();
8411 parser.set_language(&grammar).unwrap();
8412 let tree = parser.parse(source, None).unwrap();
8413
8414 let query_symbols = extract_rs_symbols_with_query_oracle(source, tree.root_node());
8415 let walk_symbols = extract_rs_symbols(source, &tree.root_node()).unwrap();
8416 assert_eq!(
8417 rust_symbol_fingerprint(&walk_symbols),
8418 rust_symbol_fingerprint(&query_symbols)
8419 );
8420 assert!(walk_symbols.iter().any(|symbol| {
8421 symbol.name == "nested" && symbol.exported && symbol.scope_chain == ["outer", "inner"]
8422 }));
8423 assert!(walk_symbols.iter().any(|symbol| {
8424 symbol.name == "attributed_method"
8425 && symbol.kind == SymbolKind::Method
8426 && symbol.scope_chain == ["Service<T> for Generic<T>"]
8427 && symbol.exported
8428 }));
8429 assert!(
8430 walk_symbols
8431 .iter()
8432 .all(|symbol| symbol.name != "declaration"),
8433 "trait declarations are not implementation methods"
8434 );
8435 }
8436
8437 #[test]
8438 fn source_parser_is_reused_on_the_current_worker_thread() {
8439 let first = "pub fn first() {}\n";
8440 let second = "pub struct Second;\n";
8441 let path = Path::new("worker.rs");
8442
8443 let before = REUSABLE_PARSERS.with(|parsers| parsers.borrow().len());
8444 let first_tree =
8445 parse_source_with_cached_parser(path, first, LangId::Rust).expect("parse first source");
8446 let after_first = REUSABLE_PARSERS.with(|parsers| parsers.borrow().len());
8447 let second_tree = parse_source_with_cached_parser(path, second, LangId::Rust)
8448 .expect("parse second source");
8449 let after_second = REUSABLE_PARSERS.with(|parsers| parsers.borrow().len());
8450
8451 assert!(!first_tree.root_node().has_error());
8452 assert!(!second_tree.root_node().has_error());
8453 assert!(after_first == before || after_first == before + 1);
8454 assert_eq!(after_second, after_first);
8455 }
8456
8457 fn test_symbol(name: &str) -> Symbol {
8458 Symbol {
8459 name: name.to_string(),
8460 kind: SymbolKind::Function,
8461 range: Range {
8462 start_line: 0,
8463 start_col: 0,
8464 end_line: 0,
8465 end_col: 10,
8466 },
8467 signature: Some(format!("fn {name}()")),
8468 scope_chain: Vec::new(),
8469 exported: true,
8470 parent: None,
8471 }
8472 }
8473
8474 #[test]
8475 fn symbol_memory_estimate_is_zero_when_empty_and_nonzero_when_populated() {
8476 let mut cache = SymbolCache::new();
8477 assert_eq!(cache.estimated_memory().estimated_bytes, Some(0));
8478 cache.insert(
8479 PathBuf::from("src/lib.rs"),
8480 SystemTime::UNIX_EPOCH,
8481 10,
8482 cache_freshness::zero_hash(),
8483 vec![test_symbol("memory_estimate")],
8484 );
8485 let estimate = cache.estimated_memory();
8486 assert!(estimate.estimated_bytes.unwrap() > 0);
8487 assert_eq!(estimate.counts["entries"], 1);
8488 assert_eq!(estimate.counts["symbols"], 1);
8489 }
8490
8491 #[test]
8492 fn inc_and_scss_extensions_are_detected() {
8493 assert_eq!(
8494 detect_language(Path::new("template.inc")),
8495 Some(LangId::Php)
8496 );
8497 assert_eq!(
8498 detect_language(Path::new("styles.scss")),
8499 Some(LangId::Scss)
8500 );
8501 assert_eq!(detect_language(Path::new("main.pas")), Some(LangId::Pascal));
8502 assert_eq!(detect_language(Path::new("main.pp")), Some(LangId::Pascal));
8503 assert_eq!(detect_language(Path::new("main.dpr")), Some(LangId::Pascal));
8504 assert_eq!(detect_language(Path::new("main.dpk")), Some(LangId::Pascal));
8505 assert_eq!(detect_language(Path::new("main.lpr")), Some(LangId::Pascal));
8506 assert_eq!(detect_language(Path::new("template.tpl")), None);
8507 }
8508
8509 #[test]
8510 fn inc_files_parse_with_php_grammar() {
8511 let tmp = tempfile::tempdir().expect("create temp dir");
8512 let file = tmp.path().join("partial.inc");
8513 std::fs::write(&file, "<?php\nfunction render_partial() { return 1; }\n")
8514 .expect("write inc file");
8515
8516 let mut parser = FileParser::new();
8517 let symbols = parser
8518 .extract_symbols(&file)
8519 .expect("extract php inc symbols");
8520 let function = symbols
8521 .iter()
8522 .find(|symbol| symbol.name == "render_partial")
8523 .expect("find PHP function in .inc");
8524 assert_eq!(function.kind, SymbolKind::Function);
8525 }
8526
8527 #[test]
8528 fn scss_symbols_include_mixin_variable_function_and_rule() {
8529 let tmp = tempfile::tempdir().expect("create temp dir");
8530 let file = tmp.path().join("styles.scss");
8531 std::fs::write(
8532 &file,
8533 r#"$brand-color: #336699;
8534
8535@mixin button-base($padding) {
8536 padding: $padding;
8537}
8538
8539@function double($value) {
8540 @return $value * 2;
8541}
8542
8543.card, .panel {
8544 color: $brand-color;
8545}
8546"#,
8547 )
8548 .expect("write scss file");
8549
8550 let mut parser = FileParser::new();
8551 let symbols = parser.extract_symbols(&file).expect("extract scss symbols");
8552 let get = |name: &str| {
8553 symbols
8554 .iter()
8555 .find(|symbol| symbol.name == name)
8556 .unwrap_or_else(|| panic!("missing {name}; got {symbols:?}"))
8557 };
8558
8559 assert_eq!(get("button-base").kind, SymbolKind::Function);
8560 assert_eq!(get("double").kind, SymbolKind::Function);
8561 assert_eq!(get("$brand-color").kind, SymbolKind::Variable);
8562 assert_eq!(get(".card, .panel").kind, SymbolKind::Class);
8563 }
8564
8565 #[test]
8566 fn symbol_cache_load_from_disk_round_trips_synthetic_entry() {
8567 let project = tempfile::tempdir().expect("create project dir");
8568 let storage = tempfile::tempdir().expect("create storage dir");
8569 let source = project.path().join("src/lib.rs");
8570 std::fs::create_dir_all(source.parent().expect("source parent"))
8571 .expect("create source dir");
8572 std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
8573 let mtime = std::fs::metadata(&source)
8574 .expect("stat source")
8575 .modified()
8576 .expect("source mtime");
8577 let content = std::fs::read(&source).expect("read source");
8578 let size = content.len() as u64;
8579 let hash = crate::cache_freshness::hash_bytes(&content);
8580
8581 let mut cache = SymbolCache::new();
8582 cache.set_project_root(project.path().to_path_buf());
8583 cache.insert(
8584 source.clone(),
8585 mtime,
8586 size,
8587 hash,
8588 vec![test_symbol("cached")],
8589 );
8590 symbol_cache_disk::write_to_disk(&cache, storage.path(), "unit-project")
8591 .expect("write symbol cache");
8592
8593 let mut restored = SymbolCache::new();
8594 let outcome =
8595 restored.load_from_disk_with_outcome(storage.path(), "unit-project", project.path());
8596 let symbols = restored.get(&source, mtime).expect("restored symbols");
8597
8598 assert_eq!(outcome.loaded, 1);
8599 assert!(!outcome.needs_persistence);
8600 assert_eq!(symbols.len(), 1);
8601 assert_eq!(symbols[0].name, "cached");
8602 }
8603
8604 #[test]
8605 fn symbol_cache_load_from_disk_marks_mtime_refresh_for_persistence() {
8606 let project = tempfile::tempdir().expect("create project dir");
8607 let storage = tempfile::tempdir().expect("create storage dir");
8608 let source = project.path().join("src/lib.rs");
8609 std::fs::create_dir_all(source.parent().expect("source parent"))
8610 .expect("create source dir");
8611 std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
8612 let metadata = std::fs::metadata(&source).expect("stat source");
8613 let mtime = metadata.modified().expect("source mtime");
8614 let content = std::fs::read(&source).expect("read source");
8615
8616 let mut cache = SymbolCache::new();
8617 cache.set_project_root(project.path().to_path_buf());
8618 cache.insert(
8619 source.clone(),
8620 mtime,
8621 content.len() as u64,
8622 crate::cache_freshness::hash_bytes(&content),
8623 vec![test_symbol("cached")],
8624 );
8625 symbol_cache_disk::write_to_disk(&cache, storage.path(), "mtime-unit-project")
8626 .expect("write symbol cache");
8627
8628 let advanced_mtime = mtime
8629 .checked_add(std::time::Duration::from_secs(2))
8630 .expect("advance source mtime");
8631 filetime::set_file_mtime(
8632 &source,
8633 filetime::FileTime::from_system_time(advanced_mtime),
8634 )
8635 .expect("touch source");
8636
8637 let mut restored = SymbolCache::new();
8638 let outcome = restored.load_from_disk_with_outcome(
8639 storage.path(),
8640 "mtime-unit-project",
8641 project.path(),
8642 );
8643
8644 assert_eq!(outcome.loaded, 1);
8645 assert!(outcome.needs_persistence);
8646 assert!(restored.get(&source, advanced_mtime).is_some());
8647 }
8648
8649 #[test]
8650 fn symbol_cache_load_from_disk_drops_stale_synthetic_entry() {
8651 let project = tempfile::tempdir().expect("create project dir");
8652 let storage = tempfile::tempdir().expect("create storage dir");
8653 let source = project.path().join("src/lib.rs");
8654 std::fs::create_dir_all(source.parent().expect("source parent"))
8655 .expect("create source dir");
8656 std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
8657 let mtime = std::fs::metadata(&source)
8658 .expect("stat source")
8659 .modified()
8660 .expect("source mtime");
8661 let content = std::fs::read(&source).expect("read source");
8662 let size = content.len() as u64;
8663 let hash = crate::cache_freshness::hash_bytes(&content);
8664
8665 let mut cache = SymbolCache::new();
8666 cache.set_project_root(project.path().to_path_buf());
8667 cache.insert(
8668 source.clone(),
8669 mtime,
8670 size,
8671 hash,
8672 vec![test_symbol("cached")],
8673 );
8674 symbol_cache_disk::write_to_disk(&cache, storage.path(), "stale-unit-project")
8675 .expect("write symbol cache");
8676
8677 std::fs::write(&source, "pub fn cached() {}\npub fn fresh() {}\n").expect("change source");
8678
8679 let mut restored = SymbolCache::new();
8680 let outcome = restored.load_from_disk_with_outcome(
8681 storage.path(),
8682 "stale-unit-project",
8683 project.path(),
8684 );
8685
8686 assert_eq!(outcome.loaded, 0);
8687 assert!(outcome.needs_persistence);
8688 assert_eq!(restored.len(), 0);
8689 }
8690
8691 #[test]
8692 fn stale_prewarm_generation_cannot_repopulate_symbol_cache_after_reset() {
8693 let project = tempfile::tempdir().expect("create project dir");
8694 let storage = tempfile::tempdir().expect("create storage dir");
8695 let source = project.path().join("src/lib.rs");
8696 std::fs::create_dir_all(source.parent().expect("source parent"))
8697 .expect("create source dir");
8698 std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
8699 let mtime = std::fs::metadata(&source)
8700 .expect("stat source")
8701 .modified()
8702 .expect("source mtime");
8703 let content = std::fs::read(&source).expect("read source");
8704 let size = content.len() as u64;
8705 let hash = crate::cache_freshness::hash_bytes(&content);
8706
8707 let mut disk_cache = SymbolCache::new();
8708 disk_cache.set_project_root(project.path().to_path_buf());
8709 disk_cache.insert(
8710 source.clone(),
8711 mtime,
8712 size,
8713 hash,
8714 vec![test_symbol("cached")],
8715 );
8716 symbol_cache_disk::write_to_disk(&disk_cache, storage.path(), "prewarm-reset")
8717 .expect("write symbol cache");
8718
8719 let shared = Arc::new(RwLock::new(SymbolCache::new()));
8720 let stale_generation = shared.write().unwrap().reset();
8721 let active_generation = shared.write().unwrap().reset();
8722 assert_ne!(stale_generation, active_generation);
8723
8724 {
8725 let mut cache = shared.write().unwrap();
8726 assert!(!cache
8727 .set_project_root_for_generation(stale_generation, project.path().to_path_buf()));
8728 assert_eq!(
8729 cache.load_from_disk_for_generation(
8730 stale_generation,
8731 storage.path(),
8732 "prewarm-reset",
8733 project.path()
8734 ),
8735 0
8736 );
8737 }
8738
8739 let mut stale_parser =
8740 FileParser::with_symbol_cache_generation(Arc::clone(&shared), Some(stale_generation));
8741 stale_parser
8742 .extract_symbols(&source)
8743 .expect("stale prewarm parses source but must not write cache");
8744
8745 let cache = shared.read().unwrap();
8746 assert_eq!(cache.generation(), active_generation);
8747 assert_eq!(cache.len(), 0);
8748 assert!(cache.project_root().is_none());
8749 assert!(!cache.contains_key(&source));
8750 }
8751
8752 #[test]
8755 fn detect_ts() {
8756 assert_eq!(
8757 detect_language(Path::new("foo.ts")),
8758 Some(LangId::TypeScript)
8759 );
8760 }
8761
8762 #[test]
8763 fn detect_tsx() {
8764 assert_eq!(detect_language(Path::new("foo.tsx")), Some(LangId::Tsx));
8765 }
8766
8767 #[test]
8768 fn detect_js() {
8769 assert_eq!(
8770 detect_language(Path::new("foo.js")),
8771 Some(LangId::JavaScript)
8772 );
8773 }
8774
8775 #[test]
8776 fn detect_jsx() {
8777 assert_eq!(
8778 detect_language(Path::new("foo.jsx")),
8779 Some(LangId::JavaScript)
8780 );
8781 }
8782
8783 #[test]
8784 fn detect_py() {
8785 assert_eq!(detect_language(Path::new("foo.py")), Some(LangId::Python));
8786 }
8787
8788 #[test]
8789 fn detect_rs() {
8790 assert_eq!(detect_language(Path::new("foo.rs")), Some(LangId::Rust));
8791 }
8792
8793 #[test]
8794 fn detect_go() {
8795 assert_eq!(detect_language(Path::new("foo.go")), Some(LangId::Go));
8796 }
8797
8798 #[test]
8799 fn detect_c() {
8800 assert_eq!(detect_language(Path::new("foo.c")), Some(LangId::C));
8801 }
8802
8803 #[test]
8804 fn detect_h() {
8805 assert_eq!(detect_language(Path::new("foo.h")), Some(LangId::C));
8806 }
8807
8808 #[test]
8809 fn detect_cc() {
8810 assert_eq!(detect_language(Path::new("foo.cc")), Some(LangId::Cpp));
8811 }
8812
8813 #[test]
8814 fn detect_cpp() {
8815 assert_eq!(detect_language(Path::new("foo.cpp")), Some(LangId::Cpp));
8816 }
8817
8818 #[test]
8819 fn detect_cxx() {
8820 assert_eq!(detect_language(Path::new("foo.cxx")), Some(LangId::Cpp));
8821 }
8822
8823 #[test]
8824 fn detect_hpp() {
8825 assert_eq!(detect_language(Path::new("foo.hpp")), Some(LangId::Cpp));
8826 }
8827
8828 #[test]
8829 fn detect_hh() {
8830 assert_eq!(detect_language(Path::new("foo.hh")), Some(LangId::Cpp));
8831 }
8832
8833 #[test]
8834 fn detect_zig() {
8835 assert_eq!(detect_language(Path::new("foo.zig")), Some(LangId::Zig));
8836 }
8837
8838 #[test]
8839 fn detect_cs() {
8840 assert_eq!(detect_language(Path::new("foo.cs")), Some(LangId::CSharp));
8841 }
8842
8843 #[test]
8844 fn detect_unknown_returns_none() {
8845 assert_eq!(detect_language(Path::new("foo.txt")), None);
8846 }
8847
8848 #[test]
8849 fn groovy_language_constant_loads_under_workspace_tree_sitter() {
8850 let mut parser = Parser::new();
8851 let language: Language = dekobon_tree_sitter_groovy::LANGUAGE.into();
8852 parser
8853 .set_language(&language)
8854 .expect("load Groovy grammar under workspace tree-sitter");
8855 let tree = parser
8856 .parse("class Greeter { def greet() { 'hi' } }", None)
8857 .expect("parse Groovy sample");
8858 assert!(
8859 !tree.root_node().has_error(),
8860 "Groovy sample should parse cleanly"
8861 );
8862 }
8863
8864 #[test]
8865 fn detect_groovy_extensions_and_jenkinsfile() {
8866 assert_eq!(
8867 detect_language(Path::new("script.groovy")),
8868 Some(LangId::Groovy)
8869 );
8870 assert_eq!(
8871 detect_language(Path::new("script.gvy")),
8872 Some(LangId::Groovy)
8873 );
8874 assert_eq!(
8875 detect_language(Path::new("script.gy")),
8876 Some(LangId::Groovy)
8877 );
8878 assert_eq!(
8879 detect_language(Path::new("shell.gsh")),
8880 Some(LangId::Groovy)
8881 );
8882 assert_eq!(
8883 detect_language(Path::new("build.gradle")),
8884 Some(LangId::Groovy)
8885 );
8886 assert_eq!(
8887 detect_language(Path::new("build.gradle.kts")),
8888 Some(LangId::Kotlin)
8889 );
8890 assert_eq!(
8891 detect_language(Path::new("Jenkinsfile")),
8892 Some(LangId::Groovy)
8893 );
8894 }
8895
8896 #[test]
8899 fn unsupported_extension_returns_invalid_request() {
8900 let path = fixture_path("sample.ts");
8902 let bad_path = path.with_extension("txt");
8903 std::fs::write(&bad_path, "hello").unwrap();
8905 let provider = TreeSitterProvider::new();
8906 let result = provider.list_symbols(&bad_path);
8907 std::fs::remove_file(&bad_path).ok();
8908 match result {
8909 Err(AftError::InvalidRequest { message }) => {
8910 assert!(
8911 message.contains("unsupported file extension"),
8912 "msg: {}",
8913 message
8914 );
8915 assert!(message.contains("txt"), "msg: {}", message);
8916 }
8917 other => panic!("expected InvalidRequest, got {:?}", other),
8918 }
8919 }
8920
8921 #[test]
8924 fn ts_extracts_all_symbol_kinds() {
8925 let provider = TreeSitterProvider::new();
8926 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
8927
8928 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
8929 assert!(
8930 names.contains(&"greet"),
8931 "missing function greet: {:?}",
8932 names
8933 );
8934 assert!(names.contains(&"add"), "missing arrow fn add: {:?}", names);
8935 assert!(
8936 names.contains(&"UserService"),
8937 "missing class UserService: {:?}",
8938 names
8939 );
8940 assert!(
8941 names.contains(&"Config"),
8942 "missing interface Config: {:?}",
8943 names
8944 );
8945 assert!(
8946 names.contains(&"Status"),
8947 "missing enum Status: {:?}",
8948 names
8949 );
8950 assert!(
8951 names.contains(&"UserId"),
8952 "missing type alias UserId: {:?}",
8953 names
8954 );
8955 assert!(
8956 names.contains(&"internalHelper"),
8957 "missing non-exported fn: {:?}",
8958 names
8959 );
8960
8961 assert!(
8963 symbols.len() >= 6,
8964 "expected ≥6 symbols, got {}: {:?}",
8965 symbols.len(),
8966 names
8967 );
8968 }
8969
8970 #[test]
8971 fn ts_symbol_kinds_correct() {
8972 let provider = TreeSitterProvider::new();
8973 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
8974
8975 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
8976
8977 assert_eq!(find("greet").kind, SymbolKind::Function);
8978 assert_eq!(find("add").kind, SymbolKind::Function); assert_eq!(find("UserService").kind, SymbolKind::Class);
8980 assert_eq!(find("Config").kind, SymbolKind::Interface);
8981 assert_eq!(find("Status").kind, SymbolKind::Enum);
8982 assert_eq!(find("UserId").kind, SymbolKind::TypeAlias);
8983 }
8984
8985 #[test]
8986 fn ts_export_detection() {
8987 let provider = TreeSitterProvider::new();
8988 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
8989
8990 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
8991
8992 assert!(find("greet").exported, "greet should be exported");
8993 assert!(find("add").exported, "add should be exported");
8994 assert!(
8995 find("UserService").exported,
8996 "UserService should be exported"
8997 );
8998 assert!(find("Config").exported, "Config should be exported");
8999 assert!(find("Status").exported, "Status should be exported");
9000 assert!(
9001 !find("internalHelper").exported,
9002 "internalHelper should not be exported"
9003 );
9004 }
9005
9006 #[test]
9007 fn ts_method_scope_chain() {
9008 let provider = TreeSitterProvider::new();
9009 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
9010
9011 let methods: Vec<&Symbol> = symbols
9012 .iter()
9013 .filter(|s| s.kind == SymbolKind::Method)
9014 .collect();
9015 assert!(!methods.is_empty(), "should have at least one method");
9016
9017 for method in &methods {
9018 assert_eq!(
9019 method.scope_chain,
9020 vec!["UserService"],
9021 "method {} should have UserService in scope chain",
9022 method.name
9023 );
9024 assert_eq!(method.parent.as_deref(), Some("UserService"));
9025 }
9026 }
9027
9028 #[test]
9029 fn ts_signatures_present() {
9030 let provider = TreeSitterProvider::new();
9031 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
9032
9033 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
9034
9035 let greet_sig = find("greet").signature.as_ref().unwrap();
9036 assert!(
9037 greet_sig.contains("greet"),
9038 "signature should contain function name: {}",
9039 greet_sig
9040 );
9041 }
9042
9043 #[test]
9044 fn ts_ranges_valid() {
9045 let provider = TreeSitterProvider::new();
9046 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
9047
9048 for s in &symbols {
9049 assert!(
9050 s.range.end_line >= s.range.start_line,
9051 "symbol {} has invalid range: {:?}",
9052 s.name,
9053 s.range
9054 );
9055 }
9056 }
9057
9058 #[test]
9061 fn js_extracts_core_symbols() {
9062 let provider = TreeSitterProvider::new();
9063 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
9064
9065 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
9066 assert!(
9067 names.contains(&"multiply"),
9068 "missing function multiply: {:?}",
9069 names
9070 );
9071 assert!(
9072 names.contains(&"divide"),
9073 "missing arrow fn divide: {:?}",
9074 names
9075 );
9076 assert!(
9077 names.contains(&"EventEmitter"),
9078 "missing class EventEmitter: {:?}",
9079 names
9080 );
9081 assert!(
9082 names.contains(&"main"),
9083 "missing default export fn main: {:?}",
9084 names
9085 );
9086
9087 assert!(
9088 symbols.len() >= 4,
9089 "expected ≥4 symbols, got {}: {:?}",
9090 symbols.len(),
9091 names
9092 );
9093 }
9094
9095 #[test]
9096 fn js_arrow_fn_correctly_named() {
9097 let provider = TreeSitterProvider::new();
9098 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
9099
9100 let divide = symbols.iter().find(|s| s.name == "divide").unwrap();
9101 assert_eq!(divide.kind, SymbolKind::Function);
9102 assert!(divide.exported, "divide should be exported");
9103
9104 let internal = symbols.iter().find(|s| s.name == "internalUtil").unwrap();
9105 assert_eq!(internal.kind, SymbolKind::Function);
9106 assert!(!internal.exported, "internalUtil should not be exported");
9107 }
9108
9109 #[test]
9110 fn js_method_scope_chain() {
9111 let provider = TreeSitterProvider::new();
9112 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
9113
9114 let methods: Vec<&Symbol> = symbols
9115 .iter()
9116 .filter(|s| s.kind == SymbolKind::Method)
9117 .collect();
9118
9119 for method in &methods {
9120 assert_eq!(
9121 method.scope_chain,
9122 vec!["EventEmitter"],
9123 "method {} should have EventEmitter in scope chain",
9124 method.name
9125 );
9126 }
9127 }
9128
9129 #[test]
9132 fn tsx_extracts_react_component() {
9133 let provider = TreeSitterProvider::new();
9134 let symbols = provider.list_symbols(&fixture_path("sample.tsx")).unwrap();
9135
9136 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
9137 assert!(
9138 names.contains(&"Button"),
9139 "missing React component Button: {:?}",
9140 names
9141 );
9142 assert!(
9143 names.contains(&"Counter"),
9144 "missing class Counter: {:?}",
9145 names
9146 );
9147 assert!(
9148 names.contains(&"formatLabel"),
9149 "missing function formatLabel: {:?}",
9150 names
9151 );
9152
9153 assert!(
9154 symbols.len() >= 2,
9155 "expected ≥2 symbols, got {}: {:?}",
9156 symbols.len(),
9157 names
9158 );
9159 }
9160
9161 #[test]
9162 fn tsx_jsx_doesnt_break_parser() {
9163 let provider = TreeSitterProvider::new();
9165 let result = provider.list_symbols(&fixture_path("sample.tsx"));
9166 assert!(
9167 result.is_ok(),
9168 "TSX parsing should succeed: {:?}",
9169 result.err()
9170 );
9171 }
9172
9173 #[test]
9176 fn resolve_symbol_finds_match() {
9177 let provider = TreeSitterProvider::new();
9178 let matches = provider
9179 .resolve_symbol(&fixture_path("sample.ts"), "greet")
9180 .unwrap();
9181 assert_eq!(matches.len(), 1);
9182 assert_eq!(matches[0].symbol.name, "greet");
9183 assert_eq!(matches[0].symbol.kind, SymbolKind::Function);
9184 }
9185
9186 #[test]
9187 fn resolve_symbol_not_found() {
9188 let provider = TreeSitterProvider::new();
9189 let result = provider.resolve_symbol(&fixture_path("sample.ts"), "nonexistent");
9190 assert!(matches!(result, Err(AftError::SymbolNotFound { .. })));
9191 }
9192
9193 #[test]
9194 fn resolve_symbol_follows_reexport_chains() {
9195 let dir = tempfile::tempdir().unwrap();
9196 let config = dir.path().join("config.ts");
9197 let barrel1 = dir.path().join("barrel1.ts");
9198 let barrel2 = dir.path().join("barrel2.ts");
9199 let barrel3 = dir.path().join("barrel3.ts");
9200 let index = dir.path().join("index.ts");
9201
9202 std::fs::write(
9203 &config,
9204 "export class Config {}\nexport default class DefaultConfig {}\n",
9205 )
9206 .unwrap();
9207 std::fs::write(
9208 &barrel1,
9209 "export { Config } from './config';\nexport { default as NamedDefault } from './config';\n",
9210 )
9211 .unwrap();
9212 std::fs::write(
9213 &barrel2,
9214 "export { Config as RenamedConfig } from './barrel1';\n",
9215 )
9216 .unwrap();
9217 std::fs::write(
9218 &barrel3,
9219 "export * from './barrel2';\nexport * from './barrel1';\n",
9220 )
9221 .unwrap();
9222 std::fs::write(
9223 &index,
9224 "export class Config {}\nexport { RenamedConfig as FinalConfig } from './barrel3';\nexport * from './barrel3';\n",
9225 )
9226 .unwrap();
9227
9228 let provider = TreeSitterProvider::new();
9229 let config_canon = std::fs::canonicalize(&config).unwrap();
9230
9231 let direct = provider.resolve_symbol(&barrel1, "Config").unwrap();
9232 assert_eq!(direct.len(), 1);
9233 assert_eq!(direct[0].symbol.name, "Config");
9234 assert_eq!(direct[0].file, config_canon.display().to_string());
9235
9236 let renamed = provider.resolve_symbol(&barrel2, "RenamedConfig").unwrap();
9237 assert_eq!(renamed.len(), 1);
9238 assert_eq!(renamed[0].symbol.name, "Config");
9239 assert_eq!(renamed[0].file, config_canon.display().to_string());
9240
9241 let wildcard_chain = provider.resolve_symbol(&index, "FinalConfig").unwrap();
9242 assert_eq!(wildcard_chain.len(), 1);
9243 assert_eq!(wildcard_chain[0].symbol.name, "Config");
9244 assert_eq!(wildcard_chain[0].file, config_canon.display().to_string());
9245
9246 let wildcard_default = provider.resolve_symbol(&index, "NamedDefault").unwrap();
9247 assert_eq!(wildcard_default.len(), 1);
9248 assert_eq!(wildcard_default[0].symbol.name, "DefaultConfig");
9249 assert_eq!(wildcard_default[0].file, config_canon.display().to_string());
9250
9251 let local = provider.resolve_symbol(&index, "Config").unwrap();
9252 assert_eq!(local.len(), 1);
9253 assert_eq!(local[0].symbol.name, "Config");
9254 assert_eq!(local[0].file, index.display().to_string());
9255 }
9256
9257 #[test]
9260 fn symbol_range_includes_rust_attributes() {
9261 let dir = tempfile::tempdir().unwrap();
9262 let path = dir.path().join("test_attrs.rs");
9263 std::fs::write(
9264 &path,
9265 "/// This is a doc comment\n#[test]\n#[cfg(test)]\nfn my_test_fn() {\n assert!(true);\n}\n",
9266 )
9267 .unwrap();
9268
9269 let provider = TreeSitterProvider::new();
9270 let matches = provider.resolve_symbol(&path, "my_test_fn").unwrap();
9271 assert_eq!(matches.len(), 1);
9272 assert_eq!(
9273 matches[0].symbol.range.start_line, 0,
9274 "symbol range should include preceding /// doc comment, got start_line={}",
9275 matches[0].symbol.range.start_line
9276 );
9277 }
9278
9279 #[test]
9280 fn symbol_range_includes_go_doc_comment() {
9281 let dir = tempfile::tempdir().unwrap();
9282 let path = dir.path().join("test_doc.go");
9283 std::fs::write(
9284 &path,
9285 "package main\n\n// MyFunc does something useful.\n// It has a multi-line doc.\nfunc MyFunc() {\n}\n",
9286 )
9287 .unwrap();
9288
9289 let provider = TreeSitterProvider::new();
9290 let matches = provider.resolve_symbol(&path, "MyFunc").unwrap();
9291 assert_eq!(matches.len(), 1);
9292 assert_eq!(
9293 matches[0].symbol.range.start_line, 2,
9294 "symbol range should include preceding doc comments, got start_line={}",
9295 matches[0].symbol.range.start_line
9296 );
9297 }
9298
9299 #[test]
9300 fn symbol_range_skips_unrelated_comments() {
9301 let dir = tempfile::tempdir().unwrap();
9302 let path = dir.path().join("test_gap.go");
9303 std::fs::write(
9304 &path,
9305 "package main\n\n// This is a standalone comment\n\nfunc Standalone() {\n}\n",
9306 )
9307 .unwrap();
9308
9309 let provider = TreeSitterProvider::new();
9310 let matches = provider.resolve_symbol(&path, "Standalone").unwrap();
9311 assert_eq!(matches.len(), 1);
9312 assert_eq!(
9313 matches[0].symbol.range.start_line, 4,
9314 "symbol range should NOT include comment separated by blank line, got start_line={}",
9315 matches[0].symbol.range.start_line
9316 );
9317 }
9318
9319 #[test]
9320 fn parse_cache_returns_same_tree() {
9321 let mut parser = FileParser::new();
9322 let path = fixture_path("sample.ts");
9323
9324 let (tree1, _) = parser.parse(&path).unwrap();
9325 let tree1_root = tree1.root_node().byte_range();
9326
9327 let (tree2, _) = parser.parse(&path).unwrap();
9328 let tree2_root = tree2.root_node().byte_range();
9329
9330 assert_eq!(tree1_root, tree2_root);
9332 }
9333
9334 #[test]
9335 fn extract_symbols_from_tree_matches_list_symbols() {
9336 let path = fixture_path("sample.rs");
9337 let source = std::fs::read_to_string(&path).unwrap();
9338
9339 let provider = TreeSitterProvider::new();
9340 let listed = provider.list_symbols(&path).unwrap();
9341
9342 let mut parser = FileParser::new();
9343 let (tree, lang) = parser.parse(&path).unwrap();
9344 let extracted = extract_symbols_from_tree(&source, tree, lang).unwrap();
9345
9346 assert_eq!(symbols_as_debug(&extracted), symbols_as_debug(&listed));
9347 }
9348
9349 fn symbols_as_debug(symbols: &[Symbol]) -> Vec<String> {
9350 symbols
9351 .iter()
9352 .map(|symbol| {
9353 format!(
9354 "{}|{:?}|{}:{}-{}:{}|{:?}|{:?}|{}|{:?}",
9355 symbol.name,
9356 symbol.kind,
9357 symbol.range.start_line,
9358 symbol.range.start_col,
9359 symbol.range.end_line,
9360 symbol.range.end_col,
9361 symbol.signature,
9362 symbol.scope_chain,
9363 symbol.exported,
9364 symbol.parent,
9365 )
9366 })
9367 .collect()
9368 }
9369
9370 #[test]
9373 fn py_extracts_all_symbols() {
9374 let provider = TreeSitterProvider::new();
9375 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
9376
9377 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
9378 assert!(
9379 names.contains(&"top_level_function"),
9380 "missing top_level_function: {:?}",
9381 names
9382 );
9383 assert!(names.contains(&"MyClass"), "missing MyClass: {:?}", names);
9384 assert!(
9385 names.contains(&"instance_method"),
9386 "missing method instance_method: {:?}",
9387 names
9388 );
9389 assert!(
9390 names.contains(&"decorated_function"),
9391 "missing decorated_function: {:?}",
9392 names
9393 );
9394
9395 assert!(
9397 symbols.len() >= 4,
9398 "expected ≥4 symbols, got {}: {:?}",
9399 symbols.len(),
9400 names
9401 );
9402 }
9403
9404 #[test]
9405 fn py_symbol_kinds_correct() {
9406 let provider = TreeSitterProvider::new();
9407 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
9408
9409 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
9410
9411 assert_eq!(find("top_level_function").kind, SymbolKind::Function);
9412 assert_eq!(find("MyClass").kind, SymbolKind::Class);
9413 assert_eq!(find("instance_method").kind, SymbolKind::Method);
9414 assert_eq!(find("decorated_function").kind, SymbolKind::Function);
9415 assert_eq!(find("OuterClass").kind, SymbolKind::Class);
9416 assert_eq!(find("InnerClass").kind, SymbolKind::Class);
9417 assert_eq!(find("inner_method").kind, SymbolKind::Method);
9418 assert_eq!(find("outer_method").kind, SymbolKind::Method);
9419 }
9420
9421 #[test]
9422 fn py_method_scope_chain() {
9423 let provider = TreeSitterProvider::new();
9424 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
9425
9426 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
9427
9428 assert_eq!(
9430 find("instance_method").scope_chain,
9431 vec!["MyClass"],
9432 "instance_method should have MyClass in scope chain"
9433 );
9434 assert_eq!(find("instance_method").parent.as_deref(), Some("MyClass"));
9435
9436 assert_eq!(
9438 find("inner_method").scope_chain,
9439 vec!["OuterClass", "InnerClass"],
9440 "inner_method should have nested scope chain"
9441 );
9442
9443 assert_eq!(
9445 find("InnerClass").scope_chain,
9446 vec!["OuterClass"],
9447 "InnerClass should have OuterClass in scope"
9448 );
9449
9450 assert!(
9452 find("top_level_function").scope_chain.is_empty(),
9453 "top-level function should have empty scope chain"
9454 );
9455 }
9456
9457 #[test]
9458 fn py_decorated_function_signature() {
9459 let provider = TreeSitterProvider::new();
9460 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
9461
9462 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
9463
9464 let sig = find("decorated_function").signature.as_ref().unwrap();
9465 assert!(
9466 sig.contains("@staticmethod"),
9467 "decorated function signature should include decorator: {}",
9468 sig
9469 );
9470 assert!(
9471 sig.contains("def decorated_function"),
9472 "signature should include function def: {}",
9473 sig
9474 );
9475 }
9476
9477 #[test]
9478 fn py_ranges_valid() {
9479 let provider = TreeSitterProvider::new();
9480 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
9481
9482 for s in &symbols {
9483 assert!(
9484 s.range.end_line >= s.range.start_line,
9485 "symbol {} has invalid range: {:?}",
9486 s.name,
9487 s.range
9488 );
9489 }
9490 }
9491
9492 #[test]
9495 fn rs_extracts_all_symbols() {
9496 let provider = TreeSitterProvider::new();
9497 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
9498
9499 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
9500 assert!(
9501 names.contains(&"public_function"),
9502 "missing public_function: {:?}",
9503 names
9504 );
9505 assert!(
9506 names.contains(&"private_function"),
9507 "missing private_function: {:?}",
9508 names
9509 );
9510 assert!(names.contains(&"MyStruct"), "missing MyStruct: {:?}", names);
9511 assert!(names.contains(&"Color"), "missing enum Color: {:?}", names);
9512 assert!(
9513 names.contains(&"Drawable"),
9514 "missing trait Drawable: {:?}",
9515 names
9516 );
9517 assert!(
9519 names.contains(&"new"),
9520 "missing impl method new: {:?}",
9521 names
9522 );
9523
9524 assert!(
9526 symbols.len() >= 6,
9527 "expected ≥6 symbols, got {}: {:?}",
9528 symbols.len(),
9529 names
9530 );
9531 }
9532
9533 #[test]
9534 fn rs_symbol_kinds_correct() {
9535 let provider = TreeSitterProvider::new();
9536 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
9537
9538 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
9539
9540 assert_eq!(find("public_function").kind, SymbolKind::Function);
9541 assert_eq!(find("private_function").kind, SymbolKind::Function);
9542 assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
9543 assert_eq!(find("Color").kind, SymbolKind::Enum);
9544 assert_eq!(find("Drawable").kind, SymbolKind::Interface); assert_eq!(find("new").kind, SymbolKind::Method);
9546 }
9547
9548 #[test]
9549 fn rs_pub_export_detection() {
9550 let provider = TreeSitterProvider::new();
9551 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
9552
9553 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
9554
9555 assert!(
9556 find("public_function").exported,
9557 "pub fn should be exported"
9558 );
9559 assert!(
9560 !find("private_function").exported,
9561 "non-pub fn should not be exported"
9562 );
9563 assert!(find("MyStruct").exported, "pub struct should be exported");
9564 assert!(find("Color").exported, "pub enum should be exported");
9565 assert!(find("Drawable").exported, "pub trait should be exported");
9566 assert!(
9567 find("new").exported,
9568 "pub fn inside impl should be exported"
9569 );
9570 assert!(
9571 !find("helper").exported,
9572 "non-pub fn inside impl should not be exported"
9573 );
9574 }
9575
9576 #[test]
9577 fn rs_impl_method_scope_chain() {
9578 let provider = TreeSitterProvider::new();
9579 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
9580
9581 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
9582
9583 assert_eq!(
9585 find("new").scope_chain,
9586 vec!["MyStruct"],
9587 "impl method should have type in scope chain"
9588 );
9589 assert_eq!(find("new").parent.as_deref(), Some("MyStruct"));
9590
9591 assert!(
9593 find("public_function").scope_chain.is_empty(),
9594 "free function should have empty scope chain"
9595 );
9596 }
9597
9598 #[test]
9599 fn rs_trait_impl_scope_chain() {
9600 let provider = TreeSitterProvider::new();
9601 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
9602
9603 let draw = symbols.iter().find(|s| s.name == "draw").unwrap();
9605 assert_eq!(
9606 draw.scope_chain,
9607 vec!["Drawable for MyStruct"],
9608 "trait impl method should have 'Trait for Type' scope"
9609 );
9610 assert_eq!(draw.parent.as_deref(), Some("MyStruct"));
9611 }
9612
9613 #[test]
9614 fn rs_ranges_valid() {
9615 let provider = TreeSitterProvider::new();
9616 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
9617
9618 for s in &symbols {
9619 assert!(
9620 s.range.end_line >= s.range.start_line,
9621 "symbol {} has invalid range: {:?}",
9622 s.name,
9623 s.range
9624 );
9625 }
9626 }
9627
9628 #[test]
9631 fn go_extracts_all_symbols() {
9632 let provider = TreeSitterProvider::new();
9633 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
9634
9635 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
9636 assert!(
9637 names.contains(&"ExportedFunction"),
9638 "missing ExportedFunction: {:?}",
9639 names
9640 );
9641 assert!(
9642 names.contains(&"unexportedFunction"),
9643 "missing unexportedFunction: {:?}",
9644 names
9645 );
9646 assert!(
9647 names.contains(&"MyStruct"),
9648 "missing struct MyStruct: {:?}",
9649 names
9650 );
9651 assert!(
9652 names.contains(&"Reader"),
9653 "missing interface Reader: {:?}",
9654 names
9655 );
9656 assert!(
9658 names.contains(&"String"),
9659 "missing receiver method String: {:?}",
9660 names
9661 );
9662
9663 assert!(
9665 symbols.len() >= 4,
9666 "expected ≥4 symbols, got {}: {:?}",
9667 symbols.len(),
9668 names
9669 );
9670 }
9671
9672 #[test]
9673 fn go_symbol_kinds_correct() {
9674 let provider = TreeSitterProvider::new();
9675 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
9676
9677 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
9678
9679 assert_eq!(find("ExportedFunction").kind, SymbolKind::Function);
9680 assert_eq!(find("unexportedFunction").kind, SymbolKind::Function);
9681 assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
9682 assert_eq!(find("Reader").kind, SymbolKind::Interface);
9683 assert_eq!(find("String").kind, SymbolKind::Method);
9684 assert_eq!(find("helper").kind, SymbolKind::Method);
9685 }
9686
9687 #[test]
9688 fn go_uppercase_export_detection() {
9689 let provider = TreeSitterProvider::new();
9690 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
9691
9692 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
9693
9694 assert!(
9695 find("ExportedFunction").exported,
9696 "ExportedFunction (uppercase) should be exported"
9697 );
9698 assert!(
9699 !find("unexportedFunction").exported,
9700 "unexportedFunction (lowercase) should not be exported"
9701 );
9702 assert!(
9703 find("MyStruct").exported,
9704 "MyStruct (uppercase) should be exported"
9705 );
9706 assert!(
9707 find("Reader").exported,
9708 "Reader (uppercase) should be exported"
9709 );
9710 assert!(
9711 find("String").exported,
9712 "String method (uppercase) should be exported"
9713 );
9714 assert!(
9715 !find("helper").exported,
9716 "helper method (lowercase) should not be exported"
9717 );
9718 }
9719
9720 #[test]
9721 fn go_receiver_method_scope_chain() {
9722 let provider = TreeSitterProvider::new();
9723 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
9724
9725 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
9726
9727 assert_eq!(
9729 find("String").scope_chain,
9730 vec!["MyStruct"],
9731 "receiver method should have type in scope chain"
9732 );
9733 assert_eq!(find("String").parent.as_deref(), Some("MyStruct"));
9734
9735 assert!(
9737 find("ExportedFunction").scope_chain.is_empty(),
9738 "regular function should have empty scope chain"
9739 );
9740 }
9741
9742 #[test]
9743 fn go_ranges_valid() {
9744 let provider = TreeSitterProvider::new();
9745 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
9746
9747 for s in &symbols {
9748 assert!(
9749 s.range.end_line >= s.range.start_line,
9750 "symbol {} has invalid range: {:?}",
9751 s.name,
9752 s.range
9753 );
9754 }
9755 }
9756
9757 #[test]
9760 fn cross_language_all_six_produce_symbols() {
9761 let provider = TreeSitterProvider::new();
9762
9763 let fixtures = [
9764 ("sample.ts", "TypeScript"),
9765 ("sample.tsx", "TSX"),
9766 ("sample.js", "JavaScript"),
9767 ("sample.py", "Python"),
9768 ("sample.rs", "Rust"),
9769 ("sample.go", "Go"),
9770 ];
9771
9772 for (fixture, lang) in &fixtures {
9773 let symbols = provider
9774 .list_symbols(&fixture_path(fixture))
9775 .unwrap_or_else(|e| panic!("{} ({}) failed: {:?}", lang, fixture, e));
9776 assert!(
9777 symbols.len() >= 2,
9778 "{} should produce ≥2 symbols, got {}: {:?}",
9779 lang,
9780 symbols.len(),
9781 symbols.iter().map(|s| &s.name).collect::<Vec<_>>()
9782 );
9783 }
9784 }
9785
9786 fn cached_freshness_fixture(
9789 content: &str,
9790 ) -> (tempfile::TempDir, PathBuf, SystemTime, u64, blake3::Hash) {
9791 let dir = tempfile::tempdir().unwrap();
9792 let file = dir.path().join("test.rs");
9793 std::fs::write(&file, content).unwrap();
9794 filetime::set_file_mtime(&file, filetime::FileTime::from_unix_time(1, 0)).unwrap();
9795 let metadata = std::fs::metadata(&file).unwrap();
9796 let mtime = metadata.modified().unwrap();
9797 let size = metadata.len();
9798 let hash = cache_freshness::hash_bytes(content.as_bytes());
9799 (dir, file, mtime, size, hash)
9800 }
9801
9802 #[cfg(debug_assertions)]
9803 #[test]
9804 fn cached_freshness_unchanged_metadata_skips_hashing() {
9805 let (_dir, file, mtime, size, hash) = cached_freshness_fixture("pub fn hello() {}\n");
9806
9807 cache_freshness::reset_hash_file_if_small_count_for_debug();
9808 assert!(cached_file_is_fresh(&file, mtime, size, hash, mtime));
9809 assert_eq!(cache_freshness::hash_file_if_small_count_for_debug(), 0);
9810 }
9811
9812 #[cfg(debug_assertions)]
9813 #[test]
9814 fn cached_freshness_changed_mtime_with_identical_content_hashes_fresh() {
9815 let (_dir, file, mtime, size, hash) = cached_freshness_fixture("pub fn hello() {}\n");
9816 filetime::set_file_mtime(&file, filetime::FileTime::from_unix_time(2, 0)).unwrap();
9817
9818 cache_freshness::reset_hash_file_if_small_count_for_debug();
9819 assert!(cached_file_is_fresh(&file, mtime, size, hash, mtime));
9820 assert_eq!(cache_freshness::hash_file_if_small_count_for_debug(), 1);
9821 }
9822
9823 #[cfg(debug_assertions)]
9824 #[test]
9825 fn cached_freshness_changed_mtime_and_same_size_content_hashes_stale() {
9826 let (_dir, file, mtime, size, hash) = cached_freshness_fixture("pub fn one() {}\n");
9827 std::fs::write(&file, "pub fn two() {}\n").unwrap();
9828 filetime::set_file_mtime(&file, filetime::FileTime::from_unix_time(2, 0)).unwrap();
9829 assert_eq!(std::fs::metadata(&file).unwrap().len(), size);
9830
9831 cache_freshness::reset_hash_file_if_small_count_for_debug();
9832 assert!(!cached_file_is_fresh(&file, mtime, size, hash, mtime));
9833 assert_eq!(cache_freshness::hash_file_if_small_count_for_debug(), 1);
9834 }
9835
9836 #[cfg(debug_assertions)]
9837 #[test]
9838 fn cached_freshness_changed_size_skips_hashing() {
9839 let (_dir, file, mtime, size, hash) = cached_freshness_fixture("pub fn hello() {}\n");
9840 std::fs::write(&file, "pub fn hello() {}\n// longer\n").unwrap();
9841
9842 cache_freshness::reset_hash_file_if_small_count_for_debug();
9843 assert!(!cached_file_is_fresh(&file, mtime, size, hash, mtime));
9844 assert_eq!(cache_freshness::hash_file_if_small_count_for_debug(), 0);
9845 }
9846
9847 #[test]
9848 fn symbol_cache_returns_cached_results_on_second_call() {
9849 let dir = tempfile::tempdir().unwrap();
9850 let file = dir.path().join("test.rs");
9851 std::fs::write(&file, "pub fn hello() {}\npub fn world() {}").unwrap();
9852
9853 let mut parser = FileParser::new();
9854
9855 let symbols1 = parser.extract_symbols(&file).unwrap();
9856 assert_eq!(symbols1.len(), 2);
9857
9858 let symbols2 = parser.extract_symbols(&file).unwrap();
9860 assert_eq!(symbols2.len(), 2);
9861 assert_eq!(symbols1[0].name, symbols2[0].name);
9862
9863 assert!(parser.symbol_cache.read().unwrap().contains_key(&file));
9865 }
9866
9867 #[test]
9868 fn symbol_cache_invalidates_on_file_change() {
9869 let dir = tempfile::tempdir().unwrap();
9870 let file = dir.path().join("test.rs");
9871 std::fs::write(&file, "pub fn hello() {}").unwrap();
9872
9873 let mut parser = FileParser::new();
9874
9875 let symbols1 = parser.extract_symbols(&file).unwrap();
9876 assert_eq!(symbols1.len(), 1);
9877 assert_eq!(symbols1[0].name, "hello");
9878
9879 std::thread::sleep(std::time::Duration::from_millis(50));
9881
9882 std::fs::write(&file, "pub fn hello() {}\npub fn goodbye() {}").unwrap();
9884
9885 let symbols2 = parser.extract_symbols(&file).unwrap();
9887 assert_eq!(symbols2.len(), 2);
9888 assert!(symbols2.iter().any(|s| s.name == "goodbye"));
9889 }
9890
9891 #[test]
9892 fn symbol_cache_invalidate_method_clears_entry() {
9893 let dir = tempfile::tempdir().unwrap();
9894 let file = dir.path().join("test.rs");
9895 std::fs::write(&file, "pub fn hello() {}").unwrap();
9896
9897 let mut parser = FileParser::new();
9898 parser.extract_symbols(&file).unwrap();
9899 assert!(parser.symbol_cache.read().unwrap().contains_key(&file));
9900
9901 parser.invalidate_symbols(&file);
9902 assert!(!parser.symbol_cache.read().unwrap().contains_key(&file));
9903 assert!(!parser.cache.contains_key(&file));
9905 }
9906
9907 #[test]
9908 fn symbol_cache_works_for_multiple_languages() {
9909 let dir = tempfile::tempdir().unwrap();
9910 let rs_file = dir.path().join("lib.rs");
9911 let ts_file = dir.path().join("app.ts");
9912 let py_file = dir.path().join("main.py");
9913
9914 std::fs::write(&rs_file, "pub fn rust_fn() {}").unwrap();
9915 std::fs::write(&ts_file, "export function tsFn() {}").unwrap();
9916 std::fs::write(&py_file, "def py_fn():\n pass").unwrap();
9917
9918 let mut parser = FileParser::new();
9919
9920 let rs_syms = parser.extract_symbols(&rs_file).unwrap();
9921 let ts_syms = parser.extract_symbols(&ts_file).unwrap();
9922 let py_syms = parser.extract_symbols(&py_file).unwrap();
9923
9924 assert!(rs_syms.iter().any(|s| s.name == "rust_fn"));
9925 assert!(ts_syms.iter().any(|s| s.name == "tsFn"));
9926 assert!(py_syms.iter().any(|s| s.name == "py_fn"));
9927
9928 assert_eq!(parser.symbol_cache.read().unwrap().len(), 3);
9930
9931 let rs_syms2 = parser.extract_symbols(&rs_file).unwrap();
9933 assert_eq!(rs_syms.len(), rs_syms2.len());
9934 }
9935
9936 #[test]
9937 fn extract_json_symbols_top_level_keys() {
9938 let dir = tempfile::tempdir().unwrap();
9939 let file = dir.path().join("package.json");
9940 std::fs::write(&file, r#"{"name": "x", "version": "1"}"#).unwrap();
9941
9942 let mut parser = FileParser::new();
9943 let symbols = parser.extract_symbols(&file).unwrap();
9944
9945 assert_eq!(symbols.len(), 2);
9946 assert!(symbols
9947 .iter()
9948 .any(|s| s.name == "name" && s.kind == SymbolKind::Variable));
9949 assert!(symbols
9950 .iter()
9951 .any(|s| s.name == "version" && s.kind == SymbolKind::Variable));
9952 }
9953
9954 #[test]
9955 fn extract_json_symbols_root_array() {
9956 let dir = tempfile::tempdir().unwrap();
9957 let file = dir.path().join("array.json");
9958 std::fs::write(&file, "[1,2,3]").unwrap();
9959
9960 let mut parser = FileParser::new();
9961 let symbols = parser.extract_symbols(&file).unwrap();
9962
9963 assert_eq!(symbols.len(), 0);
9964 }
9965
9966 #[test]
9967 fn extract_json_symbols_no_recursion_into_nested() {
9968 let dir = tempfile::tempdir().unwrap();
9969 let file = dir.path().join("nested.json");
9970 std::fs::write(&file, r#"{"scripts": {"build": "tsc"}}"#).unwrap();
9971
9972 let mut parser = FileParser::new();
9973 let symbols = parser.extract_symbols(&file).unwrap();
9974
9975 assert_eq!(symbols.len(), 1);
9976 assert_eq!(symbols[0].name, "scripts");
9977 assert!(!symbols.iter().any(|s| s.name == "build"));
9978 }
9979
9980 #[test]
9981 fn extract_scala_symbols_object_and_method() {
9982 let dir = tempfile::tempdir().unwrap();
9983 let file = dir.path().join("Greeter.scala");
9984 std::fs::write(
9985 &file,
9986 "object Greeter {\n def hello(name: String): String = s\"hi $name\"\n}",
9987 )
9988 .unwrap();
9989
9990 let mut parser = FileParser::new();
9991 let symbols = parser.extract_symbols(&file).unwrap();
9992
9993 assert!(symbols
9994 .iter()
9995 .any(|s| s.name == "Greeter" && s.kind == SymbolKind::Class));
9996 assert!(symbols.iter().any(|s| s.name == "hello"
9997 && s.kind == SymbolKind::Method
9998 && s.scope_chain == vec!["Greeter".to_string()]));
9999 }
10000
10001 #[test]
10002 fn extract_scala_symbols_class_and_trait() {
10003 let dir = tempfile::tempdir().unwrap();
10004 let file = dir.path().join("Types.scala");
10005 std::fs::write(&file, "class Foo\ntrait Bar").unwrap();
10006
10007 let mut parser = FileParser::new();
10008 let symbols = parser.extract_symbols(&file).unwrap();
10009
10010 assert!(symbols
10011 .iter()
10012 .any(|s| s.name == "Foo" && s.kind == SymbolKind::Class));
10013 assert!(symbols
10014 .iter()
10015 .any(|s| s.name == "Bar" && s.kind == SymbolKind::Interface));
10016 }
10017
10018 #[test]
10019 fn extract_yaml_symbols_k8s_resource() {
10020 let dir = tempfile::tempdir().unwrap();
10021 let file = dir.path().join("deployment.yaml");
10022 std::fs::write(
10023 &file,
10024 r#"apiVersion: apps/v1
10025kind: Deployment
10026metadata:
10027 name: nginx
10028 namespace: web
10029"#,
10030 )
10031 .unwrap();
10032
10033 let mut parser = FileParser::new();
10034 let symbols = parser.extract_symbols(&file).unwrap();
10035
10036 assert_eq!(symbols.len(), 1, "Expected 1 symbol for K8s Deployment");
10037 let sym = &symbols[0];
10038 assert_eq!(sym.name, "web/Deployment/nginx");
10039 assert_eq!(sym.kind, SymbolKind::Class);
10040 assert!(sym.exported);
10041 }
10042
10043 #[test]
10044 fn extract_yaml_symbols_k8s_no_namespace() {
10045 let dir = tempfile::tempdir().unwrap();
10046 let file = dir.path().join("service.yaml");
10047 std::fs::write(
10048 &file,
10049 r#"apiVersion: v1
10050kind: Service
10051metadata:
10052 name: nginx-svc
10053"#,
10054 )
10055 .unwrap();
10056
10057 let mut parser = FileParser::new();
10058 let symbols = parser.extract_symbols(&file).unwrap();
10059
10060 assert_eq!(symbols.len(), 1, "Expected 1 symbol for K8s Service");
10061 let sym = &symbols[0];
10062 assert_eq!(sym.name, "Service/nginx-svc");
10063 assert_eq!(sym.kind, SymbolKind::Class);
10064 }
10065
10066 #[test]
10067 fn extract_yaml_symbols_multidoc() {
10068 let dir = tempfile::tempdir().unwrap();
10069 let file = dir.path().join("multidoc.yaml");
10070 std::fs::write(
10071 &file,
10072 r#"apiVersion: apps/v1
10073kind: Deployment
10074metadata:
10075 name: a
10076---
10077apiVersion: v1
10078kind: Service
10079metadata:
10080 name: b
10081"#,
10082 )
10083 .unwrap();
10084
10085 let mut parser = FileParser::new();
10086 let symbols = parser.extract_symbols(&file).unwrap();
10087
10088 assert_eq!(symbols.len(), 2, "Expected 2 symbols for multi-doc YAML");
10089 assert!(symbols.iter().any(|s| s.name == "Deployment/a"));
10090 assert!(symbols.iter().any(|s| s.name == "Service/b"));
10091 }
10092
10093 #[test]
10094 fn extract_yaml_symbols_generic_fallback() {
10095 let dir = tempfile::tempdir().unwrap();
10096 let file = dir.path().join("compose.yaml");
10097 std::fs::write(
10098 &file,
10099 r#"version: "3"
10100services:
10101 web: {}
10102volumes:
10103 data: {}
10104"#,
10105 )
10106 .unwrap();
10107
10108 let mut parser = FileParser::new();
10109 let symbols = parser.extract_symbols(&file).unwrap();
10110
10111 assert_eq!(symbols.len(), 3, "Expected 3 symbols for generic YAML");
10113 assert!(symbols
10114 .iter()
10115 .any(|s| s.name == "version" && s.kind == SymbolKind::Variable));
10116 assert!(symbols
10117 .iter()
10118 .any(|s| s.name == "services" && s.kind == SymbolKind::Variable));
10119 assert!(symbols
10120 .iter()
10121 .any(|s| s.name == "volumes" && s.kind == SymbolKind::Variable));
10122 }
10123
10124 #[test]
10125 fn extract_yaml_symbols_empty() {
10126 let dir = tempfile::tempdir().unwrap();
10127 let file = dir.path().join("empty.yaml");
10128 std::fs::write(&file, "").unwrap();
10129
10130 let mut parser = FileParser::new();
10131 let symbols = parser.extract_symbols(&file).unwrap();
10132
10133 assert_eq!(symbols.len(), 0, "Expected 0 symbols for empty YAML");
10134 }
10135
10136 #[test]
10137 fn extract_yaml_symbols_resource_limits() {
10138 let dir = tempfile::tempdir().unwrap();
10139 let file = dir.path().join("deployment.yaml");
10140 std::fs::write(
10141 &file,
10142 r#"apiVersion: apps/v1
10143kind: Deployment
10144metadata:
10145 name: app
10146spec:
10147 template:
10148 spec:
10149 containers:
10150 - name: main
10151 image: myapp:1.0
10152 resources:
10153 limits:
10154 cpu: "2"
10155 memory: 1Gi
10156 requests:
10157 cpu: "1"
10158 memory: 512Mi
10159"#,
10160 )
10161 .unwrap();
10162
10163 let mut parser = FileParser::new();
10164 let symbols = parser.extract_symbols(&file).unwrap();
10165
10166 assert_eq!(symbols.len(), 1, "Expected 1 symbol for Deployment");
10167 let sym = &symbols[0];
10168 assert_eq!(sym.name, "Deployment/app");
10169 let sig = sym.signature.as_ref().unwrap();
10170 assert!(sig.contains("cpu="), "Signature should contain cpu= field");
10171 assert!(
10172 sig.contains("memory="),
10173 "Signature should contain memory= field"
10174 );
10175 }
10176
10177 #[test]
10178 fn extract_yaml_symbols_env_names() {
10179 let dir = tempfile::tempdir().unwrap();
10180 let file = dir.path().join("deployment.yaml");
10181 std::fs::write(
10182 &file,
10183 r#"apiVersion: apps/v1
10184kind: Deployment
10185metadata:
10186 name: app
10187spec:
10188 template:
10189 spec:
10190 containers:
10191 - name: main
10192 image: myapp:1.0
10193 env:
10194 - name: FOO
10195 value: "bar"
10196 - name: BAR
10197 value: "baz"
10198"#,
10199 )
10200 .unwrap();
10201
10202 let mut parser = FileParser::new();
10203 let symbols = parser.extract_symbols(&file).unwrap();
10204
10205 assert_eq!(symbols.len(), 1, "Expected 1 symbol for Deployment");
10206 let sym = &symbols[0];
10207 let sig = sym.signature.as_ref().unwrap();
10208 assert!(
10209 sig.contains("env=FOO,BAR"),
10210 "Signature should contain env=FOO,BAR"
10211 );
10212 }
10213
10214 #[test]
10215 fn extract_yaml_symbols_rbac_rules() {
10216 let dir = tempfile::tempdir().unwrap();
10217 let file = dir.path().join("role.yaml");
10218 std::fs::write(
10219 &file,
10220 r#"apiVersion: rbac.authorization.k8s.io/v1
10221kind: Role
10222metadata:
10223 name: reader
10224rules:
10225- apiGroups: [""]
10226 resources: [pods, services]
10227 verbs: [get, list, watch]
10228"#,
10229 )
10230 .unwrap();
10231
10232 let mut parser = FileParser::new();
10233 let symbols = parser.extract_symbols(&file).unwrap();
10234
10235 assert_eq!(symbols.len(), 1, "Expected 1 symbol for Role");
10236 let sym = &symbols[0];
10237 let sig = sym.signature.as_ref().unwrap();
10238 assert!(
10239 sig.contains("verbs=get,list,watch"),
10240 "Signature should contain verbs=get,list,watch"
10241 );
10242 assert!(
10243 sig.contains("resources=pods,services"),
10244 "Signature should contain resources=pods,services"
10245 );
10246 }
10247
10248 #[test]
10249 fn extract_yaml_symbols_argo_workflow() {
10250 let dir = tempfile::tempdir().unwrap();
10251 let file = dir.path().join("workflow.yaml");
10252 std::fs::write(
10253 &file,
10254 r#"apiVersion: argoproj.io/v1alpha1
10255kind: Workflow
10256metadata:
10257 name: hello-world
10258spec:
10259 entrypoint: main
10260 templates:
10261 - name: main
10262 container:
10263 image: alpine:3.18
10264 command: [echo]
10265 args: ["hello"]
10266 - name: print
10267 container:
10268 image: alpine:3.18
10269 command: [echo]
10270 args: ["world"]
10271"#,
10272 )
10273 .unwrap();
10274
10275 let mut parser = FileParser::new();
10276 let symbols = parser.extract_symbols(&file).unwrap();
10277
10278 assert_eq!(symbols.len(), 1, "Expected 1 symbol for Workflow");
10279 let sym = &symbols[0];
10280 assert!(
10281 sym.name.contains("Workflow"),
10282 "Symbol name should contain Workflow"
10283 );
10284 let sig = sym.signature.as_ref().unwrap();
10285 assert!(
10286 sig.contains("entrypoint=main"),
10287 "Signature should contain entrypoint=main"
10288 );
10289 assert!(
10290 sig.contains("templates=main,print"),
10291 "Signature should contain templates=main,print"
10292 );
10293 assert!(
10294 sig.contains("image=alpine:3.18"),
10295 "Signature should contain image=alpine:3.18"
10296 );
10297 assert!(
10298 sig.contains("command=echo"),
10299 "Signature should contain command=echo"
10300 );
10301 }
10302
10303 #[test]
10304 fn extract_yaml_symbols_generatename_fallback() {
10305 let dir = tempfile::tempdir().unwrap();
10306 let file = dir.path().join("workflow.yaml");
10307 std::fs::write(
10308 &file,
10309 r#"apiVersion: argoproj.io/v1alpha1
10310kind: Workflow
10311metadata:
10312 generateName: hello-
10313spec:
10314 entrypoint: main
10315 templates:
10316 - name: main
10317 container:
10318 image: alpine:3.18
10319"#,
10320 )
10321 .unwrap();
10322
10323 let mut parser = FileParser::new();
10324 let symbols = parser.extract_symbols(&file).unwrap();
10325
10326 assert_eq!(symbols.len(), 1, "Expected 1 symbol for Workflow");
10327 let sym = &symbols[0];
10328 assert!(
10329 sym.name.contains("hello-"),
10330 "Symbol name should contain generateName fallback 'hello-'"
10331 );
10332 }
10333
10334 #[test]
10335 fn detect_r_extensions_are_case_sensitive() {
10336 assert_eq!(detect_language(Path::new("analysis.R")), Some(LangId::R));
10337 assert_eq!(detect_language(Path::new("script.r")), Some(LangId::R));
10338 }
10339
10340 #[test]
10341 fn extract_r_symbols_test() {
10342 let dir = tempfile::tempdir().unwrap();
10343 let file = dir.path().join("analysis.R");
10344 std::fs::write(
10345 &file,
10346 r#"
10347# summary function
10348summarise <- function(data, column) {
10349 total <- sum(data[[column]])
10350 total
10351}
10352
10353normalise = function(x) {
10354 x / max(x)
10355}
10356
10357function(y) {
10358 y + 1
10359} -> transform_values
10360
10361threshold <- 10
10362"done" -> status
10363
10364outer <- function(values) {
10365 local_value <- 1
10366 local_value
10367}
10368"#,
10369 )
10370 .unwrap();
10371
10372 let mut parser = FileParser::new();
10373 let symbols = parser.extract_symbols(&file).unwrap();
10374
10375 let get = |name: &str| {
10376 symbols
10377 .iter()
10378 .find(|symbol| symbol.name == name)
10379 .unwrap_or_else(|| panic!("missing {name}; got {symbols:?}"))
10380 };
10381
10382 assert_eq!(get("summarise").kind, SymbolKind::Function);
10383 assert_eq!(get("normalise").kind, SymbolKind::Function);
10384 assert_eq!(get("transform_values").kind, SymbolKind::Function);
10385 assert!(
10386 symbols
10387 .iter()
10388 .all(|symbol| !symbol.name.starts_with("function(")),
10389 "rightward function assignments should use the RHS identifier: {symbols:?}"
10390 );
10391 assert_eq!(get("outer").kind, SymbolKind::Function);
10392 assert_eq!(get("threshold").kind, SymbolKind::Variable);
10393 assert_eq!(get("status").kind, SymbolKind::Variable);
10394 assert!(
10395 symbols.iter().all(|symbol| symbol.name != "local_value"),
10396 "nested assignments should not surface as top-level variables: {symbols:?}"
10397 );
10398 }
10399
10400 #[test]
10401 fn extract_pascal_symbols_test() {
10402 let dir = tempfile::tempdir().unwrap();
10403 let file = dir.path().join("MyUnit.pas");
10404 std::fs::write(
10405 &file,
10406 r#"
10407unit MyUnit;
10408
10409interface
10410
10411uses SysUtils;
10412
10413type
10414 TMyClass = class
10415 private
10416 FValue: Integer;
10417 public
10418 constructor Create;
10419 procedure DoSomething; virtual;
10420 end;
10421
10422 TMyRecord = record
10423 X, Y: Integer;
10424 end;
10425
10426 IMyInterface = interface
10427 procedure DoSomethingElse;
10428 end;
10429
10430 TMyEnum = (Red, Green, Blue);
10431
10432const
10433 UNIT_CONST = 42;
10434
10435var
10436 UnitVar: string;
10437
10438implementation
10439
10440constructor TMyClass.Create;
10441begin
10442 FValue := 0;
10443end;
10444
10445procedure TMyClass.DoSomething;
10446begin
10447end;
10448
10449procedure StandaloneProc;
10450begin
10451end;
10452
10453end.
10454"#,
10455 )
10456 .unwrap();
10457
10458 let mut parser = FileParser::new();
10459 let symbols = parser.extract_symbols(&file).unwrap();
10460
10461 let get = |name: &str| {
10462 symbols
10463 .iter()
10464 .find(|symbol| symbol.name == name)
10465 .unwrap_or_else(|| panic!("missing {name}; got {symbols:?}"))
10466 };
10467
10468 assert_eq!(get("MyUnit").kind, SymbolKind::Class);
10469 assert_eq!(get("TMyClass").kind, SymbolKind::Class);
10470 assert_eq!(get("TMyRecord").kind, SymbolKind::Struct);
10471 assert_eq!(get("IMyInterface").kind, SymbolKind::Interface);
10472 assert_eq!(get("TMyEnum").kind, SymbolKind::Enum);
10473 assert_eq!(get("UNIT_CONST").kind, SymbolKind::Variable);
10474 assert_eq!(get("UnitVar").kind, SymbolKind::Variable);
10475 assert_eq!(get("StandaloneProc").kind, SymbolKind::Function);
10476
10477 let create_methods: Vec<&Symbol> = symbols.iter().filter(|s| s.name == "Create").collect();
10479 assert_eq!(create_methods.len(), 2); for m in create_methods {
10481 assert_eq!(m.kind, SymbolKind::Method);
10482 assert_eq!(m.scope_chain, vec!["TMyClass".to_string()]);
10483 }
10484
10485 let do_something_methods: Vec<&Symbol> =
10486 symbols.iter().filter(|s| s.name == "DoSomething").collect();
10487 assert_eq!(do_something_methods.len(), 2); for m in do_something_methods {
10489 assert_eq!(m.kind, SymbolKind::Method);
10490 assert_eq!(m.scope_chain, vec!["TMyClass".to_string()]);
10491 }
10492 }
10493
10494 #[test]
10495 fn groovy_fixture_extracts_types_methods_and_properties() {
10496 let provider = TreeSitterProvider::new();
10497 let symbols = provider
10498 .list_symbols(&fixture_path("sample.groovy"))
10499 .unwrap();
10500
10501 let get = |name: &str| {
10502 symbols
10503 .iter()
10504 .find(|symbol| symbol.name == name)
10505 .unwrap_or_else(|| panic!("missing {name}; got {symbols:?}"))
10506 };
10507
10508 assert_eq!(get("GreeterSupport").kind, SymbolKind::Interface);
10509 assert_eq!(get("Named").kind, SymbolKind::Interface);
10510 assert_eq!(get("BuildStatus").kind, SymbolKind::Enum);
10511 assert_eq!(get("BuildLogic").kind, SymbolKind::Class);
10512 assert_eq!(get("action").kind, SymbolKind::Variable);
10513 assert_eq!(get("action").scope_chain, vec!["BuildLogic".to_string()]);
10514 assert_eq!(get("greet").kind, SymbolKind::Method);
10515 assert_eq!(get("greet").scope_chain, vec!["BuildLogic".to_string()]);
10516 assert_eq!(get("topLevelHelper").kind, SymbolKind::Function);
10517 assert!(get("topLevelHelper").scope_chain.is_empty());
10518
10519 assert_eq!(get("BuildLogic").range.start_line, 12);
10520 assert_eq!(get("BuildLogic").range.end_line, 23);
10521 assert_eq!(get("action").range.start_line, 14);
10522 assert_eq!(get("action").range.end_line, 14);
10523 assert_eq!(get("greet").range.start_line, 16);
10524 assert_eq!(get("greet").range.end_line, 18);
10525 assert_eq!(get("topLevelHelper").range.start_line, 25);
10526 assert_eq!(get("topLevelHelper").range.end_line, 27);
10527 }
10528
10529 #[test]
10530 fn groovy_gradle_fixture_extracts_task_declarations() {
10531 let provider = TreeSitterProvider::new();
10532 let symbols = provider
10533 .list_symbols(&fixture_path("build.gradle"))
10534 .unwrap();
10535
10536 let get = |name: &str| {
10537 symbols
10538 .iter()
10539 .find(|symbol| symbol.name == name)
10540 .unwrap_or_else(|| panic!("missing {name}; got {symbols:?}"))
10541 };
10542
10543 assert_eq!(get("smokeTest").kind, SymbolKind::Function);
10544 assert_eq!(get("smokeTest").range.start_line, 8);
10545 assert_eq!(get("smokeTest").range.end_line, 12);
10546 assert_eq!(get("ciCheck").kind, SymbolKind::Function);
10547 assert_eq!(get("ciCheck").range.start_line, 14);
10548 assert_eq!(get("ciCheck").range.end_line, 18);
10549 }
10550
10551 #[test]
10552 fn groovy_jenkinsfile_fixture_extracts_pipeline_block() {
10553 let provider = TreeSitterProvider::new();
10554 let symbols = provider.list_symbols(&fixture_path("Jenkinsfile")).unwrap();
10555
10556 let pipeline = symbols
10557 .iter()
10558 .find(|symbol| symbol.name == "pipeline")
10559 .unwrap_or_else(|| panic!("missing pipeline; got {symbols:?}"));
10560 assert_eq!(pipeline.kind, SymbolKind::Function);
10561 assert_eq!(pipeline.range.start_line, 0);
10562 assert_eq!(pipeline.range.end_line, 9);
10563 }
10564}