1use std::cell::RefCell;
2use std::collections::{HashMap, HashSet};
3use std::path::{Path, PathBuf};
4use std::sync::{Arc, LazyLock, RwLock};
5use std::time::SystemTime;
6
7use streaming_iterator::StreamingIterator;
8use tree_sitter::{Language, Node, Parser, Query, QueryCursor, Tree};
9
10use crate::cache_freshness::{self, FileFreshness, FreshnessVerdict};
11use crate::callgraph::resolve_module_path;
12use crate::error::AftError;
13use crate::symbol_cache_disk;
14use crate::symbols::{Range, Symbol, SymbolKind, SymbolMatch};
15
16const MAX_REEXPORT_DEPTH: usize = 10;
17
18const TS_QUERY: &str = r#"
21;; function declarations
22(function_declaration
23 name: (identifier) @fn.name) @fn.def
24
25;; arrow functions assigned to const/let/var
26(lexical_declaration
27 (variable_declarator
28 name: (identifier) @arrow.name
29 value: (arrow_function) @arrow.body)) @arrow.def
30
31;; class declarations
32(class_declaration
33 name: (type_identifier) @class.name) @class.def
34
35;; method definitions inside classes
36(class_declaration
37 name: (type_identifier) @method.class_name
38 body: (class_body
39 (method_definition
40 name: (property_identifier) @method.name) @method.def))
41
42;; interface declarations
43(interface_declaration
44 name: (type_identifier) @interface.name) @interface.def
45
46;; enum declarations
47(enum_declaration
48 name: (identifier) @enum.name) @enum.def
49
50;; type alias declarations
51(type_alias_declaration
52 name: (type_identifier) @type_alias.name) @type_alias.def
53
54;; top-level const/let variable declarations
55(lexical_declaration
56 (variable_declarator
57 name: (identifier) @var.name)) @var.def
58
59;; export statement wrappers (top-level only)
60(export_statement) @export.stmt
61"#;
62
63const JS_QUERY: &str = r#"
64;; function declarations
65(function_declaration
66 name: (identifier) @fn.name) @fn.def
67
68;; arrow functions assigned to const/let/var
69(lexical_declaration
70 (variable_declarator
71 name: (identifier) @arrow.name
72 value: (arrow_function) @arrow.body)) @arrow.def
73
74;; class declarations
75(class_declaration
76 name: (identifier) @class.name) @class.def
77
78;; method definitions inside classes
79(class_declaration
80 name: (identifier) @method.class_name
81 body: (class_body
82 (method_definition
83 name: (property_identifier) @method.name) @method.def))
84
85;; top-level const/let variable declarations
86(lexical_declaration
87 (variable_declarator
88 name: (identifier) @var.name)) @var.def
89
90;; export statement wrappers (top-level only)
91(export_statement) @export.stmt
92"#;
93
94const PY_QUERY: &str = r#"
95;; function definitions (top-level and nested)
96(function_definition
97 name: (identifier) @fn.name) @fn.def
98
99;; class definitions
100(class_definition
101 name: (identifier) @class.name) @class.def
102
103;; decorated definitions (wraps function_definition or class_definition)
104(decorated_definition
105 (decorator) @dec.decorator) @dec.def
106"#;
107
108const RS_QUERY: &str = r#"
109;; free functions (with optional visibility)
110(function_item
111 name: (identifier) @fn.name) @fn.def
112
113;; struct items
114(struct_item
115 name: (type_identifier) @struct.name) @struct.def
116
117;; enum items
118(enum_item
119 name: (type_identifier) @enum.name) @enum.def
120
121;; trait items
122(trait_item
123 name: (type_identifier) @trait.name) @trait.def
124
125;; impl blocks — capture the whole block to find methods
126(impl_item) @impl.def
127
128;; visibility modifiers on any item
129(visibility_modifier) @vis.mod
130"#;
131
132const GO_QUERY: &str = r#"
133;; function declarations
134(function_declaration
135 name: (identifier) @fn.name) @fn.def
136
137;; method declarations (with receiver)
138(method_declaration
139 name: (field_identifier) @method.name) @method.def
140
141;; type declarations (struct and interface)
142(type_declaration
143 (type_spec
144 name: (type_identifier) @type.name
145 type: (_) @type.body)) @type.def
146"#;
147
148const C_QUERY: &str = r#"
149;; function definitions
150(function_definition
151 declarator: (function_declarator
152 declarator: (identifier) @fn.name)) @fn.def
153
154;; function declarations / prototypes
155(declaration
156 declarator: (function_declarator
157 declarator: (identifier) @fn.name)) @fn.def
158
159;; struct declarations
160(struct_specifier
161 name: (type_identifier) @struct.name
162 body: (field_declaration_list)) @struct.def
163
164;; enum declarations
165(enum_specifier
166 name: (type_identifier) @enum.name
167 body: (enumerator_list)) @enum.def
168
169;; typedef aliases
170(type_definition
171 declarator: (type_identifier) @type.name) @type.def
172
173;; macros
174(preproc_def
175 name: (identifier) @macro.name) @macro.def
176
177(preproc_function_def
178 name: (identifier) @macro.name) @macro.def
179"#;
180
181const CPP_QUERY: &str = r#"
182;; free function definitions
183(function_definition
184 declarator: (function_declarator
185 declarator: (identifier) @fn.name)) @fn.def
186
187;; free function declarations
188(declaration
189 declarator: (function_declarator
190 declarator: (identifier) @fn.name)) @fn.def
191
192;; inline method definitions / declarations inside class bodies
193(function_definition
194 declarator: (function_declarator
195 declarator: (field_identifier) @method.name)) @method.def
196
197(field_declaration
198 declarator: (function_declarator
199 declarator: (field_identifier) @method.name)) @method.def
200
201;; qualified functions / methods
202(function_definition
203 declarator: (function_declarator
204 declarator: (qualified_identifier
205 scope: (_) @qual.scope
206 name: (identifier) @qual.name))) @qual.def
207
208(declaration
209 declarator: (function_declarator
210 declarator: (qualified_identifier
211 scope: (_) @qual.scope
212 name: (identifier) @qual.name))) @qual.def
213
214;; class / struct / enum / namespace declarations
215(class_specifier
216 name: (_) @class.name) @class.def
217
218(struct_specifier
219 name: (_) @struct.name) @struct.def
220
221(enum_specifier
222 name: (_) @enum.name) @enum.def
223
224(namespace_definition
225 name: (_) @namespace.name) @namespace.def
226
227;; template declarations
228(template_declaration
229 (class_specifier
230 name: (_) @template.class.name) @template.class.item) @template.class.def
231
232(template_declaration
233 (struct_specifier
234 name: (_) @template.struct.name) @template.struct.item) @template.struct.def
235
236(template_declaration
237 (function_definition
238 declarator: (function_declarator
239 declarator: (identifier) @template.fn.name)) @template.fn.item) @template.fn.def
240
241(template_declaration
242 (function_definition
243 declarator: (function_declarator
244 declarator: (qualified_identifier
245 scope: (_) @template.qual.scope
246 name: (identifier) @template.qual.name))) @template.qual.item) @template.qual.def
247"#;
248
249const ZIG_QUERY: &str = r#"
250;; functions
251(function_declaration
252 name: (identifier) @fn.name) @fn.def
253
254;; container declarations bound to const names
255(variable_declaration
256 (identifier) @struct.name
257 "="
258 (struct_declaration) @struct.body) @struct.def
259
260(variable_declaration
261 (identifier) @enum.name
262 "="
263 (enum_declaration) @enum.body) @enum.def
264
265(variable_declaration
266 (identifier) @union.name
267 "="
268 (union_declaration) @union.body) @union.def
269
270;; const declarations
271(variable_declaration
272 (identifier) @const.name) @const.def
273
274;; tests
275(test_declaration
276 (string) @test.name) @test.def
277
278(test_declaration
279 (identifier) @test.name) @test.def
280"#;
281
282const CSHARP_QUERY: &str = r#"
283;; types
284(class_declaration
285 name: (identifier) @class.name) @class.def
286
287(interface_declaration
288 name: (identifier) @interface.name) @interface.def
289
290(struct_declaration
291 name: (identifier) @struct.name) @struct.def
292
293(enum_declaration
294 name: (identifier) @enum.name) @enum.def
295
296;; members
297(method_declaration
298 name: (identifier) @method.name) @method.def
299
300(property_declaration
301 name: (identifier) @property.name) @property.def
302
303;; namespaces
304(namespace_declaration
305 name: (_) @namespace.name) @namespace.def
306
307(file_scoped_namespace_declaration
308 name: (_) @namespace.name) @namespace.def
309"#;
310
311const BASH_QUERY: &str = r#"
314;; function definitions (both `function foo()` and `foo()` styles)
315(function_definition
316 name: (word) @fn.name) @fn.def
317"#;
318
319const SOL_QUERY: &str = r#"
322;; contracts / libraries / interfaces
323(contract_declaration
324 name: (identifier) @contract.name) @contract.def
325
326(library_declaration
327 name: (identifier) @library.name) @library.def
328
329(interface_declaration
330 name: (identifier) @interface.name) @interface.def
331
332;; functions, modifiers, constructors
333(function_definition
334 name: (identifier) @fn.name) @fn.def
335
336(modifier_definition
337 name: (identifier) @modifier.name) @modifier.def
338
339(constructor_definition) @constructor.def
340
341;; events / errors
342(event_definition
343 name: (identifier) @event.name) @event.def
344
345(error_declaration
346 name: (identifier) @error.name) @error.def
347
348;; data types
349(struct_declaration
350 name: (identifier) @struct.name) @struct.def
351
352(enum_declaration
353 name: (identifier) @enum.name) @enum.def
354
355;; state variables (top-level inside a contract)
356(state_variable_declaration
357 name: (identifier) @var.name) @var.def
358"#;
359
360const SCALA_QUERY: &str = r#"
361;; classes / objects / traits
362(class_definition
363 name: (identifier) @class.name) @class.def
364(object_definition
365 name: (identifier) @object.name) @object.def
366(enum_definition
367 name: (_) @enum.name) @enum.def
368(trait_definition
369 name: (identifier) @trait.name) @trait.def
370;; methods (def)
371(function_definition
372 name: (identifier) @fn.name) @fn.def
373(function_declaration
374 name: (identifier) @fn.name) @fn.def
375;; vals / vars / type aliases
376(val_definition
377 pattern: (identifier) @val.name) @val.def
378(var_definition
379 pattern: (identifier) @var.name) @var.def
380(given_definition
381 name: (_) @given.name) @given.def
382(type_definition
383 name: (type_identifier) @type.name) @type.def
384"#;
385
386#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
388pub enum LangId {
389 TypeScript,
390 Tsx,
391 JavaScript,
392 Python,
393 Rust,
394 Go,
395 C,
396 Cpp,
397 Zig,
398 CSharp,
399 Bash,
400 Html,
401 Markdown,
402 Solidity,
403 Vue,
404 Json,
405 Scala,
406}
407
408pub fn detect_language(path: &Path) -> Option<LangId> {
410 let ext = path.extension()?.to_str()?;
411 match ext {
412 "ts" => Some(LangId::TypeScript),
413 "tsx" => Some(LangId::Tsx),
414 "js" | "jsx" => Some(LangId::JavaScript),
415 "py" => Some(LangId::Python),
416 "rs" => Some(LangId::Rust),
417 "go" => Some(LangId::Go),
418 "c" | "h" => Some(LangId::C),
419 "cc" | "cpp" | "cxx" | "hpp" | "hh" => Some(LangId::Cpp),
420 "zig" => Some(LangId::Zig),
421 "cs" => Some(LangId::CSharp),
422 "sh" | "bash" | "zsh" => Some(LangId::Bash),
423 "html" | "htm" => Some(LangId::Html),
424 "md" | "markdown" | "mdx" => Some(LangId::Markdown),
425 "sol" => Some(LangId::Solidity),
426 "vue" => Some(LangId::Vue),
427 "json" | "jsonc" => Some(LangId::Json),
428 "scala" | "sc" => Some(LangId::Scala),
429 _ => None,
430 }
431}
432
433pub fn grammar_for(lang: LangId) -> Language {
435 match lang {
436 LangId::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
437 LangId::Tsx => tree_sitter_typescript::LANGUAGE_TSX.into(),
438 LangId::JavaScript => tree_sitter_javascript::LANGUAGE.into(),
439 LangId::Python => tree_sitter_python::LANGUAGE.into(),
440 LangId::Rust => tree_sitter_rust::LANGUAGE.into(),
441 LangId::Go => tree_sitter_go::LANGUAGE.into(),
442 LangId::C => tree_sitter_c::LANGUAGE.into(),
443 LangId::Cpp => tree_sitter_cpp::LANGUAGE.into(),
444 LangId::Zig => tree_sitter_zig::LANGUAGE.into(),
445 LangId::CSharp => tree_sitter_c_sharp::LANGUAGE.into(),
446 LangId::Bash => tree_sitter_bash::LANGUAGE.into(),
447 LangId::Html => tree_sitter_html::LANGUAGE.into(),
448 LangId::Markdown => tree_sitter_md::LANGUAGE.into(),
449 LangId::Solidity => tree_sitter_solidity::LANGUAGE.into(),
450 LangId::Vue => tree_sitter_vue::LANGUAGE.into(),
451 LangId::Json => tree_sitter_json::LANGUAGE.into(),
452 LangId::Scala => tree_sitter_scala::LANGUAGE.into(),
453 }
454}
455
456fn query_for(lang: LangId) -> Option<&'static str> {
458 match lang {
459 LangId::TypeScript | LangId::Tsx => Some(TS_QUERY),
460 LangId::JavaScript => Some(JS_QUERY),
461 LangId::Python => Some(PY_QUERY),
462 LangId::Rust => Some(RS_QUERY),
463 LangId::Go => Some(GO_QUERY),
464 LangId::C => Some(C_QUERY),
465 LangId::Cpp => Some(CPP_QUERY),
466 LangId::Zig => Some(ZIG_QUERY),
467 LangId::CSharp => Some(CSHARP_QUERY),
468 LangId::Bash => Some(BASH_QUERY),
469 LangId::Html => None, LangId::Markdown => None,
471 LangId::Solidity => Some(SOL_QUERY),
472 LangId::Vue => None,
473 LangId::Json => None,
474 LangId::Scala => Some(SCALA_QUERY),
475 }
476}
477
478static TS_QUERY_CACHE: LazyLock<Result<Query, String>> =
479 LazyLock::new(|| compile_query(LangId::TypeScript));
480static TSX_QUERY_CACHE: LazyLock<Result<Query, String>> =
481 LazyLock::new(|| compile_query(LangId::Tsx));
482static JS_QUERY_CACHE: LazyLock<Result<Query, String>> =
483 LazyLock::new(|| compile_query(LangId::JavaScript));
484static PY_QUERY_CACHE: LazyLock<Result<Query, String>> =
485 LazyLock::new(|| compile_query(LangId::Python));
486static RS_QUERY_CACHE: LazyLock<Result<Query, String>> =
487 LazyLock::new(|| compile_query(LangId::Rust));
488static GO_QUERY_CACHE: LazyLock<Result<Query, String>> =
489 LazyLock::new(|| compile_query(LangId::Go));
490static C_QUERY_CACHE: LazyLock<Result<Query, String>> = LazyLock::new(|| compile_query(LangId::C));
491static CPP_QUERY_CACHE: LazyLock<Result<Query, String>> =
492 LazyLock::new(|| compile_query(LangId::Cpp));
493static ZIG_QUERY_CACHE: LazyLock<Result<Query, String>> =
494 LazyLock::new(|| compile_query(LangId::Zig));
495static CSHARP_QUERY_CACHE: LazyLock<Result<Query, String>> =
496 LazyLock::new(|| compile_query(LangId::CSharp));
497static BASH_QUERY_CACHE: LazyLock<Result<Query, String>> =
498 LazyLock::new(|| compile_query(LangId::Bash));
499static SOL_QUERY_CACHE: LazyLock<Result<Query, String>> =
500 LazyLock::new(|| compile_query(LangId::Solidity));
501static SCALA_QUERY_CACHE: LazyLock<Result<Query, String>> =
502 LazyLock::new(|| compile_query(LangId::Scala));
503
504fn compile_query(lang: LangId) -> Result<Query, String> {
505 let query_src = query_for(lang).ok_or_else(|| format!("missing query for {lang:?}"))?;
506 let grammar = grammar_for(lang);
507 Query::new(&grammar, query_src)
508 .map_err(|error| format!("query compile error for {lang:?}: {error}"))
509}
510
511fn cached_query_for(lang: LangId) -> Result<Option<&'static Query>, AftError> {
512 let query = match lang {
513 LangId::TypeScript => Some(&*TS_QUERY_CACHE),
514 LangId::Tsx => Some(&*TSX_QUERY_CACHE),
515 LangId::JavaScript => Some(&*JS_QUERY_CACHE),
516 LangId::Python => Some(&*PY_QUERY_CACHE),
517 LangId::Rust => Some(&*RS_QUERY_CACHE),
518 LangId::Go => Some(&*GO_QUERY_CACHE),
519 LangId::C => Some(&*C_QUERY_CACHE),
520 LangId::Cpp => Some(&*CPP_QUERY_CACHE),
521 LangId::Zig => Some(&*ZIG_QUERY_CACHE),
522 LangId::CSharp => Some(&*CSHARP_QUERY_CACHE),
523 LangId::Bash => Some(&*BASH_QUERY_CACHE),
524 LangId::Solidity => Some(&*SOL_QUERY_CACHE),
525 LangId::Scala => Some(&*SCALA_QUERY_CACHE),
526 LangId::Html | LangId::Markdown | LangId::Vue | LangId::Json => None,
527 };
528
529 query
530 .map(|result| {
531 result.as_ref().map_err(|message| AftError::ParseError {
532 message: message.clone(),
533 })
534 })
535 .transpose()
536}
537
538struct CachedTree {
540 mtime: SystemTime,
541 tree: Tree,
542}
543
544#[derive(Clone)]
546struct CachedSymbols {
547 mtime: SystemTime,
548 size: u64,
549 content_hash: blake3::Hash,
550 symbols: Vec<Symbol>,
551}
552
553#[derive(Clone, Default)]
556pub struct SymbolCache {
557 entries: HashMap<PathBuf, CachedSymbols>,
558 generation: u64,
559 project_root: Option<PathBuf>,
560}
561
562pub type SharedSymbolCache = Arc<RwLock<SymbolCache>>;
563
564impl SymbolCache {
565 pub fn new() -> Self {
566 Self {
567 entries: HashMap::new(),
568 generation: 0,
569 project_root: None,
570 }
571 }
572
573 pub fn set_project_root(&mut self, project_root: PathBuf) {
575 debug_assert!(project_root.is_absolute());
576 self.project_root = Some(project_root);
577 }
578
579 pub fn set_project_root_for_generation(
582 &mut self,
583 generation: u64,
584 project_root: PathBuf,
585 ) -> bool {
586 if self.generation != generation {
587 return false;
588 }
589 self.set_project_root(project_root);
590 true
591 }
592
593 pub fn insert(
595 &mut self,
596 path: PathBuf,
597 mtime: SystemTime,
598 size: u64,
599 content_hash: blake3::Hash,
600 symbols: Vec<Symbol>,
601 ) {
602 self.entries.insert(
603 path,
604 CachedSymbols {
605 mtime,
606 size,
607 content_hash,
608 symbols,
609 },
610 );
611 }
612
613 pub fn insert_for_generation(
615 &mut self,
616 generation: u64,
617 path: PathBuf,
618 mtime: SystemTime,
619 size: u64,
620 content_hash: blake3::Hash,
621 symbols: Vec<Symbol>,
622 ) -> bool {
623 if self.generation != generation {
624 return false;
625 }
626 self.insert(path, mtime, size, content_hash, symbols);
627 true
628 }
629
630 pub fn get(&self, path: &Path, mtime: SystemTime) -> Option<Vec<Symbol>> {
632 self.entries
633 .get(path)
634 .and_then(|cached| (cached.mtime == mtime).then(|| cached.symbols.clone()))
635 }
636
637 pub fn contains_path_with_mtime(&self, path: &Path, mtime: SystemTime) -> bool {
639 self.entries
640 .get(path)
641 .is_some_and(|cached| cached.mtime == mtime)
642 }
643
644 pub fn load_from_disk(
646 &mut self,
647 storage_dir: &Path,
648 project_key: &str,
649 current_root: &Path,
650 ) -> usize {
651 debug_assert!(current_root.is_absolute());
652 let Some(cache) = symbol_cache_disk::read_from_disk(storage_dir, project_key) else {
653 return 0;
654 };
655
656 self.project_root = Some(current_root.to_path_buf());
657 self.entries.clear();
658 let mut loaded = 0usize;
659
660 for entry in cache.entries {
661 let path = current_root.join(&entry.relative_path);
662 let cached_freshness = FileFreshness {
663 mtime: entry.mtime,
664 size: entry.size,
665 content_hash: entry.content_hash,
666 };
667 let mtime = match cache_freshness::verify_file(&path, &cached_freshness) {
668 FreshnessVerdict::HotFresh => entry.mtime,
669 FreshnessVerdict::ContentFresh { new_mtime, .. } => new_mtime,
670 FreshnessVerdict::Stale | FreshnessVerdict::Deleted => continue,
671 };
672
673 self.entries.insert(
674 path,
675 CachedSymbols {
676 mtime,
677 size: entry.size,
678 content_hash: entry.content_hash,
679 symbols: entry.symbols,
680 },
681 );
682 loaded += 1;
683 }
684
685 loaded
686 }
687
688 pub fn load_from_disk_for_generation(
691 &mut self,
692 generation: u64,
693 storage_dir: &Path,
694 project_key: &str,
695 current_root: &Path,
696 ) -> usize {
697 if self.generation != generation {
698 return 0;
699 }
700 self.load_from_disk(storage_dir, project_key, current_root)
701 }
702
703 pub fn invalidate(&mut self, path: &Path) {
705 self.entries.remove(path);
706 }
707
708 pub fn reset(&mut self) -> u64 {
710 self.entries.clear();
711 self.project_root = None;
712 self.generation = self.generation.wrapping_add(1);
713 self.generation
714 }
715
716 pub fn generation(&self) -> u64 {
718 self.generation
719 }
720
721 pub fn contains_key(&self, path: &Path) -> bool {
723 self.entries.contains_key(path)
724 }
725
726 pub fn len(&self) -> usize {
728 self.entries.len()
729 }
730
731 pub(crate) fn project_root(&self) -> Option<PathBuf> {
732 self.project_root.clone()
733 }
734
735 pub(crate) fn disk_entries(
736 &self,
737 ) -> Vec<(&PathBuf, SystemTime, u64, blake3::Hash, &Vec<Symbol>)> {
738 self.entries
739 .iter()
740 .filter_map(|(path, cached)| {
741 if cached.symbols.is_empty() {
742 return None;
743 }
744 Some((
745 path,
746 cached.mtime,
747 cached.size,
748 cached.content_hash,
749 &cached.symbols,
750 ))
751 })
752 .collect()
753 }
754}
755
756pub struct FileParser {
759 cache: HashMap<PathBuf, CachedTree>,
760 parsers: HashMap<LangId, Parser>,
761 symbol_cache: SharedSymbolCache,
762 symbol_cache_generation: Option<u64>,
763}
764
765impl FileParser {
766 pub fn new() -> Self {
768 Self::with_symbol_cache(Arc::new(RwLock::new(SymbolCache::new())))
769 }
770
771 pub fn with_symbol_cache(symbol_cache: SharedSymbolCache) -> Self {
773 Self::with_symbol_cache_generation(symbol_cache, None)
774 }
775
776 pub fn with_symbol_cache_generation(
778 symbol_cache: SharedSymbolCache,
779 symbol_cache_generation: Option<u64>,
780 ) -> Self {
781 Self {
782 cache: HashMap::new(),
783 parsers: HashMap::new(),
784 symbol_cache,
785 symbol_cache_generation,
786 }
787 }
788
789 fn parser_for(&mut self, lang: LangId) -> Result<&mut Parser, AftError> {
790 use std::collections::hash_map::Entry;
791
792 match self.parsers.entry(lang) {
793 Entry::Occupied(entry) => Ok(entry.into_mut()),
794 Entry::Vacant(entry) => {
795 let grammar = grammar_for(lang);
796 let mut parser = Parser::new();
797 parser.set_language(&grammar).map_err(|e| {
798 crate::slog_error!("grammar init failed for {:?}: {}", lang, e);
799 AftError::ParseError {
800 message: format!("grammar init failed for {:?}: {}", lang, e),
801 }
802 })?;
803 Ok(entry.insert(parser))
804 }
805 }
806 }
807
808 pub fn symbol_cache_len(&self) -> usize {
810 self.symbol_cache
811 .read()
812 .map(|cache| cache.len())
813 .unwrap_or(0)
814 }
815
816 pub fn symbol_cache(&self) -> SharedSymbolCache {
818 Arc::clone(&self.symbol_cache)
819 }
820
821 pub fn parse(&mut self, path: &Path) -> Result<(&Tree, LangId), AftError> {
824 let lang = detect_language(path).ok_or_else(|| AftError::InvalidRequest {
825 message: format!(
826 "unsupported file extension: {}",
827 path.extension()
828 .and_then(|e| e.to_str())
829 .unwrap_or("<none>")
830 ),
831 })?;
832
833 let canon = path.to_path_buf();
834 let current_mtime = std::fs::metadata(path)
835 .and_then(|m| m.modified())
836 .map_err(|e| AftError::FileNotFound {
837 path: format!("{}: {}", path.display(), e),
838 })?;
839
840 let needs_reparse = match self.cache.get(&canon) {
842 Some(cached) => cached.mtime != current_mtime,
843 None => true,
844 };
845
846 if needs_reparse {
847 let source = std::fs::read_to_string(path).map_err(|e| AftError::FileNotFound {
848 path: format!("{}: {}", path.display(), e),
849 })?;
850
851 let tree = self.parser_for(lang)?.parse(&source, None).ok_or_else(|| {
852 crate::slog_error!("parse failed for {}", path.display());
853 AftError::ParseError {
854 message: format!("tree-sitter parse returned None for {}", path.display()),
855 }
856 })?;
857
858 self.cache.insert(
859 canon.clone(),
860 CachedTree {
861 mtime: current_mtime,
862 tree,
863 },
864 );
865 }
866
867 let cached = self.cache.get(&canon).ok_or_else(|| AftError::ParseError {
868 message: format!("parser cache missing entry for {}", path.display()),
869 })?;
870 Ok((&cached.tree, lang))
871 }
872
873 pub fn parse_cloned(&mut self, path: &Path) -> Result<(Tree, LangId), AftError> {
878 let (tree, lang) = self.parse(path)?;
879 Ok((tree.clone(), lang))
880 }
881
882 pub fn extract_symbols(&mut self, path: &Path) -> Result<Vec<Symbol>, AftError> {
886 let canon = path.to_path_buf();
887 let current_mtime = std::fs::metadata(path)
888 .and_then(|m| m.modified())
889 .map_err(|e| AftError::FileNotFound {
890 path: format!("{}: {}", path.display(), e),
891 })?;
892
893 if let Some(symbols) = self
895 .symbol_cache
896 .read()
897 .map_err(|_| AftError::ParseError {
898 message: "symbol cache lock poisoned".to_string(),
899 })?
900 .get(&canon, current_mtime)
901 {
902 return Ok(symbols);
903 }
904
905 let source = std::fs::read_to_string(path).map_err(|e| AftError::FileNotFound {
906 path: format!("{}: {}", path.display(), e),
907 })?;
908 let size = source.len() as u64;
909 let content_hash = cache_freshness::hash_bytes(source.as_bytes());
910
911 let symbols = {
912 let (tree, lang) = self.parse(path)?;
913 extract_symbols_from_tree(&source, tree, lang)?
914 };
915
916 let mut symbol_cache = self
918 .symbol_cache
919 .write()
920 .map_err(|_| AftError::ParseError {
921 message: "symbol cache lock poisoned".to_string(),
922 })?;
923 if let Some(generation) = self.symbol_cache_generation {
924 symbol_cache.insert_for_generation(
925 generation,
926 canon,
927 current_mtime,
928 size,
929 content_hash,
930 symbols.clone(),
931 );
932 } else {
933 symbol_cache.insert(canon, current_mtime, size, content_hash, symbols.clone());
934 }
935
936 Ok(symbols)
937 }
938
939 pub fn invalidate_symbols(&mut self, path: &Path) {
941 if let Ok(mut symbol_cache) = self.symbol_cache.write() {
942 symbol_cache.invalidate(path);
943 }
944 self.cache.remove(path);
945 }
946}
947
948pub fn extract_symbols_from_tree(
953 source: &str,
954 tree: &Tree,
955 lang: LangId,
956) -> Result<Vec<Symbol>, AftError> {
957 let root = tree.root_node();
958
959 if lang == LangId::Html {
960 return extract_html_symbols(source, &root);
961 }
962 if lang == LangId::Markdown {
963 return extract_md_symbols(source, &root);
964 }
965 if lang == LangId::Vue {
966 return extract_vue_symbols(source, &root);
967 }
968 if lang == LangId::Json {
969 return extract_json_symbols(source, &root);
970 }
971
972 let query = cached_query_for(lang)?.ok_or_else(|| AftError::InvalidRequest {
973 message: format!("no query patterns implemented for {:?} yet", lang),
974 })?;
975
976 match lang {
977 LangId::TypeScript | LangId::Tsx => extract_ts_symbols(source, &root, query),
978 LangId::JavaScript => extract_js_symbols(source, &root, query),
979 LangId::Python => extract_py_symbols(source, &root, query),
980 LangId::Rust => extract_rs_symbols(source, &root, query),
981 LangId::Go => extract_go_symbols(source, &root, query),
982 LangId::C => extract_c_symbols(source, &root, query),
983 LangId::Cpp => extract_cpp_symbols(source, &root, query),
984 LangId::Zig => extract_zig_symbols(source, &root, query),
985 LangId::CSharp => extract_csharp_symbols(source, &root, query),
986 LangId::Bash => extract_bash_symbols(source, &root, query),
987 LangId::Solidity => extract_solidity_symbols(source, &root, query),
988 LangId::Scala => extract_scala_symbols(source, &root, query),
989 LangId::Html | LangId::Markdown | LangId::Vue | LangId::Json => {
990 unreachable!("handled before query lookup")
991 }
992 }
993}
994
995pub(crate) fn node_range(node: &Node) -> Range {
997 let start = node.start_position();
998 let end = node.end_position();
999 Range {
1000 start_line: start.row as u32,
1001 start_col: start.column as u32,
1002 end_line: end.row as u32,
1003 end_col: end.column as u32,
1004 }
1005}
1006
1007pub(crate) fn node_range_with_decorators(node: &Node, source: &str, lang: LangId) -> Range {
1013 if matches!(lang, LangId::Python) {
1014 if let Some(parent) = node.parent() {
1015 if parent.kind() == "decorated_definition" {
1016 return node_range(&parent);
1017 }
1018 }
1019 }
1020
1021 if matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
1029 if let Some(parent) = node.parent() {
1030 if parent.kind() == "export_statement" {
1031 return node_range_with_decorators_inner(&parent, source, lang);
1032 }
1033 }
1034 }
1035
1036 node_range_with_decorators_inner(node, source, lang)
1037}
1038
1039fn node_range_with_decorators_inner(node: &Node, source: &str, lang: LangId) -> Range {
1043 let mut range = node_range(node);
1044
1045 let mut current = *node;
1046 while let Some(prev) = current.prev_sibling() {
1047 let kind = prev.kind();
1048 let should_include = match lang {
1049 LangId::Rust => {
1050 kind == "attribute_item"
1052 || (kind == "line_comment"
1054 && node_text(source, &prev).starts_with("///"))
1055 || (kind == "block_comment"
1057 && node_text(source, &prev).starts_with("/**"))
1058 }
1059 LangId::TypeScript | LangId::Tsx | LangId::JavaScript => {
1060 kind == "decorator"
1062 || (kind == "comment"
1064 && node_text(source, &prev).starts_with("/**"))
1065 }
1066 LangId::Go | LangId::C | LangId::Cpp | LangId::Zig | LangId::CSharp | LangId::Bash => {
1067 kind == "comment" && is_adjacent_line(&prev, ¤t, source)
1069 }
1070 LangId::Solidity | LangId::Scala => {
1071 let text = node_text(source, &prev);
1073 kind == "comment"
1074 && (text.starts_with("///") || text.starts_with("/**"))
1075 && is_adjacent_line(&prev, ¤t, source)
1076 }
1077 LangId::Python => {
1078 false
1080 }
1081 LangId::Html | LangId::Markdown | LangId::Vue | LangId::Json => false,
1082 };
1083
1084 if should_include {
1085 range.start_line = prev.start_position().row as u32;
1086 range.start_col = prev.start_position().column as u32;
1087 current = prev;
1088 } else {
1089 break;
1090 }
1091 }
1092
1093 range
1094}
1095
1096fn is_adjacent_line(upper: &Node, lower: &Node, source: &str) -> bool {
1098 let upper_end = upper.end_position().row;
1099 let lower_start = lower.start_position().row;
1100
1101 if lower_start == 0 || lower_start <= upper_end {
1102 return true;
1103 }
1104
1105 let lines: Vec<&str> = source.lines().collect();
1107 for row in (upper_end + 1)..lower_start {
1108 if row < lines.len() && lines[row].trim().is_empty() {
1109 return false;
1110 }
1111 }
1112 true
1113}
1114
1115pub(crate) fn node_text<'a>(source: &'a str, node: &Node) -> &'a str {
1117 &source[node.byte_range()]
1118}
1119
1120fn lexical_declaration_has_function_value(node: &Node) -> bool {
1121 let mut cursor = node.walk();
1122 if !cursor.goto_first_child() {
1123 return false;
1124 }
1125
1126 loop {
1127 let child = cursor.node();
1128 if matches!(
1129 child.kind(),
1130 "arrow_function" | "function_expression" | "generator_function"
1131 ) {
1132 return true;
1133 }
1134
1135 if lexical_declaration_has_function_value(&child) {
1136 return true;
1137 }
1138
1139 if !cursor.goto_next_sibling() {
1140 break;
1141 }
1142 }
1143
1144 false
1145}
1146
1147fn collect_export_ranges(source: &str, root: &Node, query: &Query) -> Vec<std::ops::Range<usize>> {
1149 let export_idx = query
1150 .capture_names()
1151 .iter()
1152 .position(|n| *n == "export.stmt");
1153 let export_idx = match export_idx {
1154 Some(i) => i as u32,
1155 None => return vec![],
1156 };
1157
1158 let mut cursor = QueryCursor::new();
1159 let mut ranges = Vec::new();
1160 let mut matches = cursor.matches(query, *root, source.as_bytes());
1161
1162 while let Some(m) = {
1163 matches.advance();
1164 matches.get()
1165 } {
1166 for cap in m.captures {
1167 if cap.index == export_idx {
1168 ranges.push(cap.node.byte_range());
1169 }
1170 }
1171 }
1172 ranges
1173}
1174
1175fn is_exported(node: &Node, export_ranges: &[std::ops::Range<usize>]) -> bool {
1177 let r = node.byte_range();
1178 export_ranges
1179 .iter()
1180 .any(|er| er.start <= r.start && r.end <= er.end)
1181}
1182
1183fn extract_signature(source: &str, node: &Node) -> String {
1185 let text = node_text(source, node);
1186 let first_line = text.lines().next().unwrap_or(text);
1187 let trimmed = first_line.trim_end();
1189 let trimmed = trimmed.strip_suffix('{').unwrap_or(trimmed).trim_end();
1190 trimmed.to_string()
1191}
1192
1193fn extract_ts_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1195 let lang = LangId::TypeScript;
1196 let capture_names = query.capture_names();
1197
1198 let export_ranges = collect_export_ranges(source, root, query);
1199
1200 let mut symbols = Vec::new();
1201 let mut cursor = QueryCursor::new();
1202 let mut matches = cursor.matches(query, *root, source.as_bytes());
1203
1204 while let Some(m) = {
1205 matches.advance();
1206 matches.get()
1207 } {
1208 let mut fn_name_node = None;
1210 let mut fn_def_node = None;
1211 let mut arrow_name_node = None;
1212 let mut arrow_def_node = None;
1213 let mut class_name_node = None;
1214 let mut class_def_node = None;
1215 let mut method_class_name_node = None;
1216 let mut method_name_node = None;
1217 let mut method_def_node = None;
1218 let mut interface_name_node = None;
1219 let mut interface_def_node = None;
1220 let mut enum_name_node = None;
1221 let mut enum_def_node = None;
1222 let mut type_alias_name_node = None;
1223 let mut type_alias_def_node = None;
1224 let mut var_name_node = None;
1225 let mut var_def_node = None;
1226
1227 for cap in m.captures {
1228 let Some(&name) = capture_names.get(cap.index as usize) else {
1229 continue;
1230 };
1231 match name {
1232 "fn.name" => fn_name_node = Some(cap.node),
1233 "fn.def" => fn_def_node = Some(cap.node),
1234 "arrow.name" => arrow_name_node = Some(cap.node),
1235 "arrow.def" => arrow_def_node = Some(cap.node),
1236 "class.name" => class_name_node = Some(cap.node),
1237 "class.def" => class_def_node = Some(cap.node),
1238 "method.class_name" => method_class_name_node = Some(cap.node),
1239 "method.name" => method_name_node = Some(cap.node),
1240 "method.def" => method_def_node = Some(cap.node),
1241 "interface.name" => interface_name_node = Some(cap.node),
1242 "interface.def" => interface_def_node = Some(cap.node),
1243 "enum.name" => enum_name_node = Some(cap.node),
1244 "enum.def" => enum_def_node = Some(cap.node),
1245 "type_alias.name" => type_alias_name_node = Some(cap.node),
1246 "type_alias.def" => type_alias_def_node = Some(cap.node),
1247 "var.name" => var_name_node = Some(cap.node),
1248 "var.def" => var_def_node = Some(cap.node),
1249 _ => {}
1251 }
1252 }
1253
1254 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1256 symbols.push(Symbol {
1257 name: node_text(source, &name_node).to_string(),
1258 kind: SymbolKind::Function,
1259 range: node_range_with_decorators(&def_node, source, lang),
1260 signature: Some(extract_signature(source, &def_node)),
1261 scope_chain: vec![],
1262 exported: is_exported(&def_node, &export_ranges),
1263 parent: None,
1264 });
1265 }
1266
1267 if let (Some(name_node), Some(def_node)) = (arrow_name_node, arrow_def_node) {
1269 symbols.push(Symbol {
1270 name: node_text(source, &name_node).to_string(),
1271 kind: SymbolKind::Function,
1272 range: node_range_with_decorators(&def_node, source, lang),
1273 signature: Some(extract_signature(source, &def_node)),
1274 scope_chain: vec![],
1275 exported: is_exported(&def_node, &export_ranges),
1276 parent: None,
1277 });
1278 }
1279
1280 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
1282 symbols.push(Symbol {
1283 name: node_text(source, &name_node).to_string(),
1284 kind: SymbolKind::Class,
1285 range: node_range_with_decorators(&def_node, source, lang),
1286 signature: Some(extract_signature(source, &def_node)),
1287 scope_chain: vec![],
1288 exported: is_exported(&def_node, &export_ranges),
1289 parent: None,
1290 });
1291 }
1292
1293 if let (Some(class_name_node), Some(name_node), Some(def_node)) =
1295 (method_class_name_node, method_name_node, method_def_node)
1296 {
1297 let class_name = node_text(source, &class_name_node).to_string();
1298 symbols.push(Symbol {
1299 name: node_text(source, &name_node).to_string(),
1300 kind: SymbolKind::Method,
1301 range: node_range_with_decorators(&def_node, source, lang),
1302 signature: Some(extract_signature(source, &def_node)),
1303 scope_chain: vec![class_name.clone()],
1304 exported: false, parent: Some(class_name),
1306 });
1307 }
1308
1309 if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
1311 symbols.push(Symbol {
1312 name: node_text(source, &name_node).to_string(),
1313 kind: SymbolKind::Interface,
1314 range: node_range_with_decorators(&def_node, source, lang),
1315 signature: Some(extract_signature(source, &def_node)),
1316 scope_chain: vec![],
1317 exported: is_exported(&def_node, &export_ranges),
1318 parent: None,
1319 });
1320 }
1321
1322 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
1324 symbols.push(Symbol {
1325 name: node_text(source, &name_node).to_string(),
1326 kind: SymbolKind::Enum,
1327 range: node_range_with_decorators(&def_node, source, lang),
1328 signature: Some(extract_signature(source, &def_node)),
1329 scope_chain: vec![],
1330 exported: is_exported(&def_node, &export_ranges),
1331 parent: None,
1332 });
1333 }
1334
1335 if let (Some(name_node), Some(def_node)) = (type_alias_name_node, type_alias_def_node) {
1337 symbols.push(Symbol {
1338 name: node_text(source, &name_node).to_string(),
1339 kind: SymbolKind::TypeAlias,
1340 range: node_range_with_decorators(&def_node, source, lang),
1341 signature: Some(extract_signature(source, &def_node)),
1342 scope_chain: vec![],
1343 exported: is_exported(&def_node, &export_ranges),
1344 parent: None,
1345 });
1346 }
1347
1348 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
1350 let is_top_level = def_node
1352 .parent()
1353 .map(|p| p.kind() == "program" || p.kind() == "export_statement")
1354 .unwrap_or(false);
1355 let is_function_like = lexical_declaration_has_function_value(&def_node);
1356 let name = node_text(source, &name_node).to_string();
1357 let already_captured = symbols.iter().any(|s| s.name == name);
1358 if is_top_level && !is_function_like && !already_captured {
1359 symbols.push(Symbol {
1360 name,
1361 kind: SymbolKind::Variable,
1362 range: node_range_with_decorators(&def_node, source, lang),
1363 signature: Some(extract_signature(source, &def_node)),
1364 scope_chain: vec![],
1365 exported: is_exported(&def_node, &export_ranges),
1366 parent: None,
1367 });
1368 }
1369 }
1370 }
1371
1372 dedup_symbols(&mut symbols);
1374 Ok(symbols)
1375}
1376
1377fn extract_js_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1379 let lang = LangId::JavaScript;
1380 let capture_names = query.capture_names();
1381
1382 let export_ranges = collect_export_ranges(source, root, query);
1383
1384 let mut symbols = Vec::new();
1385 let mut cursor = QueryCursor::new();
1386 let mut matches = cursor.matches(query, *root, source.as_bytes());
1387
1388 while let Some(m) = {
1389 matches.advance();
1390 matches.get()
1391 } {
1392 let mut fn_name_node = None;
1393 let mut fn_def_node = None;
1394 let mut arrow_name_node = None;
1395 let mut arrow_def_node = None;
1396 let mut class_name_node = None;
1397 let mut class_def_node = None;
1398 let mut method_class_name_node = None;
1399 let mut method_name_node = None;
1400 let mut method_def_node = None;
1401
1402 for cap in m.captures {
1403 let Some(&name) = capture_names.get(cap.index as usize) else {
1404 continue;
1405 };
1406 match name {
1407 "fn.name" => fn_name_node = Some(cap.node),
1408 "fn.def" => fn_def_node = Some(cap.node),
1409 "arrow.name" => arrow_name_node = Some(cap.node),
1410 "arrow.def" => arrow_def_node = Some(cap.node),
1411 "class.name" => class_name_node = Some(cap.node),
1412 "class.def" => class_def_node = Some(cap.node),
1413 "method.class_name" => method_class_name_node = Some(cap.node),
1414 "method.name" => method_name_node = Some(cap.node),
1415 "method.def" => method_def_node = Some(cap.node),
1416 _ => {}
1417 }
1418 }
1419
1420 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1421 symbols.push(Symbol {
1422 name: node_text(source, &name_node).to_string(),
1423 kind: SymbolKind::Function,
1424 range: node_range_with_decorators(&def_node, source, lang),
1425 signature: Some(extract_signature(source, &def_node)),
1426 scope_chain: vec![],
1427 exported: is_exported(&def_node, &export_ranges),
1428 parent: None,
1429 });
1430 }
1431
1432 if let (Some(name_node), Some(def_node)) = (arrow_name_node, arrow_def_node) {
1433 symbols.push(Symbol {
1434 name: node_text(source, &name_node).to_string(),
1435 kind: SymbolKind::Function,
1436 range: node_range_with_decorators(&def_node, source, lang),
1437 signature: Some(extract_signature(source, &def_node)),
1438 scope_chain: vec![],
1439 exported: is_exported(&def_node, &export_ranges),
1440 parent: None,
1441 });
1442 }
1443
1444 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
1445 symbols.push(Symbol {
1446 name: node_text(source, &name_node).to_string(),
1447 kind: SymbolKind::Class,
1448 range: node_range_with_decorators(&def_node, source, lang),
1449 signature: Some(extract_signature(source, &def_node)),
1450 scope_chain: vec![],
1451 exported: is_exported(&def_node, &export_ranges),
1452 parent: None,
1453 });
1454 }
1455
1456 if let (Some(class_name_node), Some(name_node), Some(def_node)) =
1457 (method_class_name_node, method_name_node, method_def_node)
1458 {
1459 let class_name = node_text(source, &class_name_node).to_string();
1460 symbols.push(Symbol {
1461 name: node_text(source, &name_node).to_string(),
1462 kind: SymbolKind::Method,
1463 range: node_range_with_decorators(&def_node, source, lang),
1464 signature: Some(extract_signature(source, &def_node)),
1465 scope_chain: vec![class_name.clone()],
1466 exported: false,
1467 parent: Some(class_name),
1468 });
1469 }
1470 }
1471
1472 dedup_symbols(&mut symbols);
1473 Ok(symbols)
1474}
1475
1476fn py_scope_chain(node: &Node, source: &str) -> Vec<String> {
1479 let mut chain = Vec::new();
1480 let mut current = node.parent();
1481 while let Some(parent) = current {
1482 if parent.kind() == "class_definition" {
1483 if let Some(name_node) = parent.child_by_field_name("name") {
1484 chain.push(node_text(source, &name_node).to_string());
1485 }
1486 }
1487 current = parent.parent();
1488 }
1489 chain.reverse();
1490 chain
1491}
1492
1493fn extract_py_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1495 let lang = LangId::Python;
1496 let capture_names = query.capture_names();
1497
1498 let mut symbols = Vec::new();
1499 let mut cursor = QueryCursor::new();
1500 let mut matches = cursor.matches(query, *root, source.as_bytes());
1501
1502 let mut decorated_fn_lines = std::collections::HashSet::new();
1504
1505 {
1507 let mut cursor2 = QueryCursor::new();
1508 let mut matches2 = cursor2.matches(query, *root, source.as_bytes());
1509 while let Some(m) = {
1510 matches2.advance();
1511 matches2.get()
1512 } {
1513 let mut dec_def_node = None;
1514 let mut dec_decorator_node = None;
1515
1516 for cap in m.captures {
1517 let Some(&name) = capture_names.get(cap.index as usize) else {
1518 continue;
1519 };
1520 match name {
1521 "dec.def" => dec_def_node = Some(cap.node),
1522 "dec.decorator" => dec_decorator_node = Some(cap.node),
1523 _ => {}
1524 }
1525 }
1526
1527 if let (Some(def_node), Some(_dec_node)) = (dec_def_node, dec_decorator_node) {
1528 let mut child_cursor = def_node.walk();
1530 if child_cursor.goto_first_child() {
1531 loop {
1532 let child = child_cursor.node();
1533 if child.kind() == "function_definition"
1534 || child.kind() == "class_definition"
1535 {
1536 decorated_fn_lines.insert(child.start_position().row);
1537 }
1538 if !child_cursor.goto_next_sibling() {
1539 break;
1540 }
1541 }
1542 }
1543 }
1544 }
1545 }
1546
1547 while let Some(m) = {
1548 matches.advance();
1549 matches.get()
1550 } {
1551 let mut fn_name_node = None;
1552 let mut fn_def_node = None;
1553 let mut class_name_node = None;
1554 let mut class_def_node = None;
1555
1556 for cap in m.captures {
1557 let Some(&name) = capture_names.get(cap.index as usize) else {
1558 continue;
1559 };
1560 match name {
1561 "fn.name" => fn_name_node = Some(cap.node),
1562 "fn.def" => fn_def_node = Some(cap.node),
1563 "class.name" => class_name_node = Some(cap.node),
1564 "class.def" => class_def_node = Some(cap.node),
1565 _ => {}
1566 }
1567 }
1568
1569 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1571 let scope = py_scope_chain(&def_node, source);
1572 let is_method = !scope.is_empty();
1573 let name = node_text(source, &name_node).to_string();
1574 let kind = if is_method {
1576 SymbolKind::Method
1577 } else {
1578 SymbolKind::Function
1579 };
1580
1581 let sig = if decorated_fn_lines.contains(&def_node.start_position().row) {
1583 let mut sig_parts = Vec::new();
1585 let mut parent = def_node.parent();
1586 while let Some(p) = parent {
1587 if p.kind() == "decorated_definition" {
1588 let mut dc = p.walk();
1590 if dc.goto_first_child() {
1591 loop {
1592 if dc.node().kind() == "decorator" {
1593 sig_parts.push(node_text(source, &dc.node()).to_string());
1594 }
1595 if !dc.goto_next_sibling() {
1596 break;
1597 }
1598 }
1599 }
1600 break;
1601 }
1602 parent = p.parent();
1603 }
1604 sig_parts.push(extract_signature(source, &def_node));
1605 Some(sig_parts.join("\n"))
1606 } else {
1607 Some(extract_signature(source, &def_node))
1608 };
1609
1610 symbols.push(Symbol {
1611 name,
1612 kind,
1613 range: node_range_with_decorators(&def_node, source, lang),
1614 signature: sig,
1615 scope_chain: scope.clone(),
1616 exported: false, parent: scope.last().cloned(),
1618 });
1619 }
1620
1621 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
1623 let scope = py_scope_chain(&def_node, source);
1624
1625 let sig = if decorated_fn_lines.contains(&def_node.start_position().row) {
1627 let mut sig_parts = Vec::new();
1628 let mut parent = def_node.parent();
1629 while let Some(p) = parent {
1630 if p.kind() == "decorated_definition" {
1631 let mut dc = p.walk();
1632 if dc.goto_first_child() {
1633 loop {
1634 if dc.node().kind() == "decorator" {
1635 sig_parts.push(node_text(source, &dc.node()).to_string());
1636 }
1637 if !dc.goto_next_sibling() {
1638 break;
1639 }
1640 }
1641 }
1642 break;
1643 }
1644 parent = p.parent();
1645 }
1646 sig_parts.push(extract_signature(source, &def_node));
1647 Some(sig_parts.join("\n"))
1648 } else {
1649 Some(extract_signature(source, &def_node))
1650 };
1651
1652 symbols.push(Symbol {
1653 name: node_text(source, &name_node).to_string(),
1654 kind: SymbolKind::Class,
1655 range: node_range_with_decorators(&def_node, source, lang),
1656 signature: sig,
1657 scope_chain: scope.clone(),
1658 exported: false,
1659 parent: scope.last().cloned(),
1660 });
1661 }
1662 }
1663
1664 dedup_symbols(&mut symbols);
1665 Ok(symbols)
1666}
1667
1668fn extract_rs_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1671 let lang = LangId::Rust;
1672 let capture_names = query.capture_names();
1673
1674 let mut vis_ranges: Vec<std::ops::Range<usize>> = Vec::new();
1676 {
1677 let vis_idx = capture_names.iter().position(|n| *n == "vis.mod");
1678 if let Some(idx) = vis_idx {
1679 let idx = idx as u32;
1680 let mut cursor = QueryCursor::new();
1681 let mut matches = cursor.matches(query, *root, source.as_bytes());
1682 while let Some(m) = {
1683 matches.advance();
1684 matches.get()
1685 } {
1686 for cap in m.captures {
1687 if cap.index == idx {
1688 vis_ranges.push(cap.node.byte_range());
1689 }
1690 }
1691 }
1692 }
1693 }
1694
1695 let is_pub = |node: &Node| -> bool {
1696 let mut child_cursor = node.walk();
1698 if child_cursor.goto_first_child() {
1699 loop {
1700 if child_cursor.node().kind() == "visibility_modifier" {
1701 return true;
1702 }
1703 if !child_cursor.goto_next_sibling() {
1704 break;
1705 }
1706 }
1707 }
1708 false
1709 };
1710
1711 let mut symbols = Vec::new();
1712 let mut cursor = QueryCursor::new();
1713 let mut matches = cursor.matches(query, *root, source.as_bytes());
1714
1715 while let Some(m) = {
1716 matches.advance();
1717 matches.get()
1718 } {
1719 let mut fn_name_node = None;
1720 let mut fn_def_node = None;
1721 let mut struct_name_node = None;
1722 let mut struct_def_node = None;
1723 let mut enum_name_node = None;
1724 let mut enum_def_node = None;
1725 let mut trait_name_node = None;
1726 let mut trait_def_node = None;
1727 let mut impl_def_node = None;
1728
1729 for cap in m.captures {
1730 let Some(&name) = capture_names.get(cap.index as usize) else {
1731 continue;
1732 };
1733 match name {
1734 "fn.name" => fn_name_node = Some(cap.node),
1735 "fn.def" => fn_def_node = Some(cap.node),
1736 "struct.name" => struct_name_node = Some(cap.node),
1737 "struct.def" => struct_def_node = Some(cap.node),
1738 "enum.name" => enum_name_node = Some(cap.node),
1739 "enum.def" => enum_def_node = Some(cap.node),
1740 "trait.name" => trait_name_node = Some(cap.node),
1741 "trait.def" => trait_def_node = Some(cap.node),
1742 "impl.def" => impl_def_node = Some(cap.node),
1743 _ => {}
1744 }
1745 }
1746
1747 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1749 let parent = def_node.parent();
1750 let in_impl = parent
1751 .map(|p| p.kind() == "declaration_list")
1752 .unwrap_or(false);
1753 if !in_impl {
1754 symbols.push(Symbol {
1755 name: node_text(source, &name_node).to_string(),
1756 kind: SymbolKind::Function,
1757 range: node_range_with_decorators(&def_node, source, lang),
1758 signature: Some(extract_signature(source, &def_node)),
1759 scope_chain: vec![],
1760 exported: is_pub(&def_node),
1761 parent: None,
1762 });
1763 }
1764 }
1765
1766 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
1768 symbols.push(Symbol {
1769 name: node_text(source, &name_node).to_string(),
1770 kind: SymbolKind::Struct,
1771 range: node_range_with_decorators(&def_node, source, lang),
1772 signature: Some(extract_signature(source, &def_node)),
1773 scope_chain: vec![],
1774 exported: is_pub(&def_node),
1775 parent: None,
1776 });
1777 }
1778
1779 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
1781 symbols.push(Symbol {
1782 name: node_text(source, &name_node).to_string(),
1783 kind: SymbolKind::Enum,
1784 range: node_range_with_decorators(&def_node, source, lang),
1785 signature: Some(extract_signature(source, &def_node)),
1786 scope_chain: vec![],
1787 exported: is_pub(&def_node),
1788 parent: None,
1789 });
1790 }
1791
1792 if let (Some(name_node), Some(def_node)) = (trait_name_node, trait_def_node) {
1794 symbols.push(Symbol {
1795 name: node_text(source, &name_node).to_string(),
1796 kind: SymbolKind::Interface,
1797 range: node_range_with_decorators(&def_node, source, lang),
1798 signature: Some(extract_signature(source, &def_node)),
1799 scope_chain: vec![],
1800 exported: is_pub(&def_node),
1801 parent: None,
1802 });
1803 }
1804
1805 if let Some(impl_node) = impl_def_node {
1807 let mut type_names: Vec<String> = Vec::new();
1811 let mut child_cursor = impl_node.walk();
1812 if child_cursor.goto_first_child() {
1813 loop {
1814 let child = child_cursor.node();
1815 if child.kind() == "type_identifier" || child.kind() == "generic_type" {
1816 type_names.push(node_text(source, &child).to_string());
1817 }
1818 if !child_cursor.goto_next_sibling() {
1819 break;
1820 }
1821 }
1822 }
1823
1824 let scope_name = if type_names.len() >= 2 {
1825 format!("{} for {}", type_names[0], type_names[1])
1827 } else if type_names.len() == 1 {
1828 type_names[0].clone()
1829 } else {
1830 String::new()
1831 };
1832
1833 let parent_name = type_names.last().cloned().unwrap_or_default();
1834
1835 let mut child_cursor = impl_node.walk();
1837 if child_cursor.goto_first_child() {
1838 loop {
1839 let child = child_cursor.node();
1840 if child.kind() == "declaration_list" {
1841 let mut fn_cursor = child.walk();
1842 if fn_cursor.goto_first_child() {
1843 loop {
1844 let fn_node = fn_cursor.node();
1845 if fn_node.kind() == "function_item" {
1846 if let Some(name_node) = fn_node.child_by_field_name("name") {
1847 symbols.push(Symbol {
1848 name: node_text(source, &name_node).to_string(),
1849 kind: SymbolKind::Method,
1850 range: node_range_with_decorators(
1851 &fn_node, source, lang,
1852 ),
1853 signature: Some(extract_signature(source, &fn_node)),
1854 scope_chain: if scope_name.is_empty() {
1855 vec![]
1856 } else {
1857 vec![scope_name.clone()]
1858 },
1859 exported: is_pub(&fn_node),
1860 parent: if parent_name.is_empty() {
1861 None
1862 } else {
1863 Some(parent_name.clone())
1864 },
1865 });
1866 }
1867 }
1868 if !fn_cursor.goto_next_sibling() {
1869 break;
1870 }
1871 }
1872 }
1873 }
1874 if !child_cursor.goto_next_sibling() {
1875 break;
1876 }
1877 }
1878 }
1879 }
1880 }
1881
1882 dedup_symbols(&mut symbols);
1883 Ok(symbols)
1884}
1885
1886fn extract_go_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1890 let lang = LangId::Go;
1891 let capture_names = query.capture_names();
1892
1893 let is_go_exported = |name: &str| -> bool {
1894 name.chars()
1895 .next()
1896 .map(|c| c.is_uppercase())
1897 .unwrap_or(false)
1898 };
1899
1900 let mut symbols = Vec::new();
1901 let mut cursor = QueryCursor::new();
1902 let mut matches = cursor.matches(query, *root, source.as_bytes());
1903
1904 while let Some(m) = {
1905 matches.advance();
1906 matches.get()
1907 } {
1908 let mut fn_name_node = None;
1909 let mut fn_def_node = None;
1910 let mut method_name_node = None;
1911 let mut method_def_node = None;
1912 let mut type_name_node = None;
1913 let mut type_body_node = None;
1914 let mut type_def_node = None;
1915
1916 for cap in m.captures {
1917 let Some(&name) = capture_names.get(cap.index as usize) else {
1918 continue;
1919 };
1920 match name {
1921 "fn.name" => fn_name_node = Some(cap.node),
1922 "fn.def" => fn_def_node = Some(cap.node),
1923 "method.name" => method_name_node = Some(cap.node),
1924 "method.def" => method_def_node = Some(cap.node),
1925 "type.name" => type_name_node = Some(cap.node),
1926 "type.body" => type_body_node = Some(cap.node),
1927 "type.def" => type_def_node = Some(cap.node),
1928 _ => {}
1929 }
1930 }
1931
1932 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1934 let name = node_text(source, &name_node).to_string();
1935 symbols.push(Symbol {
1936 exported: is_go_exported(&name),
1937 name,
1938 kind: SymbolKind::Function,
1939 range: node_range_with_decorators(&def_node, source, lang),
1940 signature: Some(extract_signature(source, &def_node)),
1941 scope_chain: vec![],
1942 parent: None,
1943 });
1944 }
1945
1946 if let (Some(name_node), Some(def_node)) = (method_name_node, method_def_node) {
1948 let name = node_text(source, &name_node).to_string();
1949
1950 let receiver_type = extract_go_receiver_type(&def_node, source);
1952 let scope_chain = if let Some(ref rt) = receiver_type {
1953 vec![rt.clone()]
1954 } else {
1955 vec![]
1956 };
1957
1958 symbols.push(Symbol {
1959 exported: is_go_exported(&name),
1960 name,
1961 kind: SymbolKind::Method,
1962 range: node_range_with_decorators(&def_node, source, lang),
1963 signature: Some(extract_signature(source, &def_node)),
1964 scope_chain,
1965 parent: receiver_type,
1966 });
1967 }
1968
1969 if let (Some(name_node), Some(body_node), Some(def_node)) =
1971 (type_name_node, type_body_node, type_def_node)
1972 {
1973 let name = node_text(source, &name_node).to_string();
1974 let kind = match body_node.kind() {
1975 "struct_type" => SymbolKind::Struct,
1976 "interface_type" => SymbolKind::Interface,
1977 _ => SymbolKind::TypeAlias,
1978 };
1979
1980 symbols.push(Symbol {
1981 exported: is_go_exported(&name),
1982 name,
1983 kind,
1984 range: node_range_with_decorators(&def_node, source, lang),
1985 signature: Some(extract_signature(source, &def_node)),
1986 scope_chain: vec![],
1987 parent: None,
1988 });
1989 }
1990 }
1991
1992 dedup_symbols(&mut symbols);
1993 Ok(symbols)
1994}
1995
1996fn extract_go_receiver_type(method_node: &Node, source: &str) -> Option<String> {
1999 let mut child_cursor = method_node.walk();
2001 if child_cursor.goto_first_child() {
2002 loop {
2003 let child = child_cursor.node();
2004 if child.kind() == "parameter_list" {
2005 return find_type_identifier_recursive(&child, source);
2007 }
2008 if !child_cursor.goto_next_sibling() {
2009 break;
2010 }
2011 }
2012 }
2013 None
2014}
2015
2016fn split_scope_text(text: &str, separator: &str) -> Vec<String> {
2017 text.split(separator)
2018 .map(str::trim)
2019 .filter(|segment| !segment.is_empty())
2020 .map(ToString::to_string)
2021 .collect()
2022}
2023
2024fn last_scope_segment(text: &str, separator: &str) -> String {
2025 split_scope_text(text, separator)
2026 .pop()
2027 .unwrap_or_else(|| text.trim().to_string())
2028}
2029
2030fn zig_container_scope_chain(node: &Node, source: &str) -> Vec<String> {
2031 let mut chain = Vec::new();
2032 let mut current = node.parent();
2033
2034 while let Some(parent) = current {
2035 if matches!(
2036 parent.kind(),
2037 "struct_declaration" | "enum_declaration" | "union_declaration" | "opaque_declaration"
2038 ) {
2039 if let Some(container) = parent.parent() {
2040 if container.kind() == "variable_declaration" {
2041 let mut cursor = container.walk();
2042 if cursor.goto_first_child() {
2043 loop {
2044 let child = cursor.node();
2045 if child.kind() == "identifier" {
2046 chain.push(node_text(source, &child).to_string());
2047 break;
2048 }
2049 if !cursor.goto_next_sibling() {
2050 break;
2051 }
2052 }
2053 }
2054 }
2055 }
2056 }
2057 current = parent.parent();
2058 }
2059
2060 chain.reverse();
2061 chain
2062}
2063
2064fn csharp_scope_chain(node: &Node, source: &str) -> Vec<String> {
2065 let mut chain = Vec::new();
2066 let mut current = node.parent();
2067
2068 while let Some(parent) = current {
2069 match parent.kind() {
2070 "namespace_declaration" | "file_scoped_namespace_declaration" => {
2071 if let Some(name_node) = parent.child_by_field_name("name") {
2072 chain.push(node_text(source, &name_node).to_string());
2073 }
2074 }
2075 "class_declaration"
2076 | "interface_declaration"
2077 | "struct_declaration"
2078 | "record_declaration" => {
2079 if let Some(name_node) = parent.child_by_field_name("name") {
2080 chain.push(node_text(source, &name_node).to_string());
2081 }
2082 }
2083 _ => {}
2084 }
2085 current = parent.parent();
2086 }
2087
2088 chain.reverse();
2089 chain
2090}
2091
2092fn cpp_parent_scope_chain(node: &Node, source: &str) -> Vec<String> {
2093 let mut chain = Vec::new();
2094 let mut current = node.parent();
2095
2096 while let Some(parent) = current {
2097 match parent.kind() {
2098 "namespace_definition" => {
2099 if let Some(name_node) = parent.child_by_field_name("name") {
2100 chain.push(node_text(source, &name_node).to_string());
2101 }
2102 }
2103 "class_specifier" | "struct_specifier" => {
2104 if let Some(name_node) = parent.child_by_field_name("name") {
2105 chain.push(last_scope_segment(node_text(source, &name_node), "::"));
2106 }
2107 }
2108 _ => {}
2109 }
2110 current = parent.parent();
2111 }
2112
2113 chain.reverse();
2114 chain
2115}
2116
2117fn template_signature(source: &str, template_node: &Node, item_node: &Node) -> String {
2118 format!(
2119 "{}\n{}",
2120 extract_signature(source, template_node),
2121 extract_signature(source, item_node)
2122 )
2123}
2124
2125fn extract_c_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2127 let lang = LangId::C;
2128 let capture_names = query.capture_names();
2129
2130 let mut symbols = Vec::new();
2131 let mut cursor = QueryCursor::new();
2132 let mut matches = cursor.matches(query, *root, source.as_bytes());
2133
2134 while let Some(m) = {
2135 matches.advance();
2136 matches.get()
2137 } {
2138 let mut fn_name_node = None;
2139 let mut fn_def_node = None;
2140 let mut struct_name_node = None;
2141 let mut struct_def_node = None;
2142 let mut enum_name_node = None;
2143 let mut enum_def_node = None;
2144 let mut type_name_node = None;
2145 let mut type_def_node = None;
2146 let mut macro_name_node = None;
2147 let mut macro_def_node = None;
2148
2149 for cap in m.captures {
2150 let Some(&name) = capture_names.get(cap.index as usize) else {
2151 continue;
2152 };
2153 match name {
2154 "fn.name" => fn_name_node = Some(cap.node),
2155 "fn.def" => fn_def_node = Some(cap.node),
2156 "struct.name" => struct_name_node = Some(cap.node),
2157 "struct.def" => struct_def_node = Some(cap.node),
2158 "enum.name" => enum_name_node = Some(cap.node),
2159 "enum.def" => enum_def_node = Some(cap.node),
2160 "type.name" => type_name_node = Some(cap.node),
2161 "type.def" => type_def_node = Some(cap.node),
2162 "macro.name" => macro_name_node = Some(cap.node),
2163 "macro.def" => macro_def_node = Some(cap.node),
2164 _ => {}
2165 }
2166 }
2167
2168 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2169 symbols.push(Symbol {
2170 name: node_text(source, &name_node).to_string(),
2171 kind: SymbolKind::Function,
2172 range: node_range_with_decorators(&def_node, source, lang),
2173 signature: Some(extract_signature(source, &def_node)),
2174 scope_chain: vec![],
2175 exported: false,
2176 parent: None,
2177 });
2178 }
2179
2180 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
2181 symbols.push(Symbol {
2182 name: node_text(source, &name_node).to_string(),
2183 kind: SymbolKind::Struct,
2184 range: node_range_with_decorators(&def_node, source, lang),
2185 signature: Some(extract_signature(source, &def_node)),
2186 scope_chain: vec![],
2187 exported: false,
2188 parent: None,
2189 });
2190 }
2191
2192 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
2193 symbols.push(Symbol {
2194 name: node_text(source, &name_node).to_string(),
2195 kind: SymbolKind::Enum,
2196 range: node_range_with_decorators(&def_node, source, lang),
2197 signature: Some(extract_signature(source, &def_node)),
2198 scope_chain: vec![],
2199 exported: false,
2200 parent: None,
2201 });
2202 }
2203
2204 if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
2205 symbols.push(Symbol {
2206 name: node_text(source, &name_node).to_string(),
2207 kind: SymbolKind::TypeAlias,
2208 range: node_range_with_decorators(&def_node, source, lang),
2209 signature: Some(extract_signature(source, &def_node)),
2210 scope_chain: vec![],
2211 exported: false,
2212 parent: None,
2213 });
2214 }
2215
2216 if let (Some(name_node), Some(def_node)) = (macro_name_node, macro_def_node) {
2217 symbols.push(Symbol {
2218 name: node_text(source, &name_node).to_string(),
2219 kind: SymbolKind::Variable,
2220 range: node_range(&def_node),
2221 signature: Some(extract_signature(source, &def_node)),
2222 scope_chain: vec![],
2223 exported: false,
2224 parent: None,
2225 });
2226 }
2227 }
2228
2229 dedup_symbols(&mut symbols);
2230 Ok(symbols)
2231}
2232
2233fn extract_cpp_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2235 let lang = LangId::Cpp;
2236 let capture_names = query.capture_names();
2237
2238 let mut type_names = HashSet::new();
2239 {
2240 let mut cursor = QueryCursor::new();
2241 let mut matches = cursor.matches(query, *root, source.as_bytes());
2242 while let Some(m) = {
2243 matches.advance();
2244 matches.get()
2245 } {
2246 for cap in m.captures {
2247 let Some(&name) = capture_names.get(cap.index as usize) else {
2248 continue;
2249 };
2250 match name {
2251 "class.name"
2252 | "struct.name"
2253 | "template.class.name"
2254 | "template.struct.name" => {
2255 type_names.insert(last_scope_segment(node_text(source, &cap.node), "::"));
2256 }
2257 _ => {}
2258 }
2259 }
2260 }
2261 }
2262
2263 let mut symbols = Vec::new();
2264 let mut cursor = QueryCursor::new();
2265 let mut matches = cursor.matches(query, *root, source.as_bytes());
2266
2267 while let Some(m) = {
2268 matches.advance();
2269 matches.get()
2270 } {
2271 let mut fn_name_node = None;
2272 let mut fn_def_node = None;
2273 let mut method_name_node = None;
2274 let mut method_def_node = None;
2275 let mut qual_scope_node = None;
2276 let mut qual_name_node = None;
2277 let mut qual_def_node = None;
2278 let mut class_name_node = None;
2279 let mut class_def_node = None;
2280 let mut struct_name_node = None;
2281 let mut struct_def_node = None;
2282 let mut enum_name_node = None;
2283 let mut enum_def_node = None;
2284 let mut namespace_name_node = None;
2285 let mut namespace_def_node = None;
2286 let mut template_class_name_node = None;
2287 let mut template_class_def_node = None;
2288 let mut template_class_item_node = None;
2289 let mut template_struct_name_node = None;
2290 let mut template_struct_def_node = None;
2291 let mut template_struct_item_node = None;
2292 let mut template_fn_name_node = None;
2293 let mut template_fn_def_node = None;
2294 let mut template_fn_item_node = None;
2295 let mut template_qual_scope_node = None;
2296 let mut template_qual_name_node = None;
2297 let mut template_qual_def_node = None;
2298 let mut template_qual_item_node = None;
2299
2300 for cap in m.captures {
2301 let Some(&name) = capture_names.get(cap.index as usize) else {
2302 continue;
2303 };
2304 match name {
2305 "fn.name" => fn_name_node = Some(cap.node),
2306 "fn.def" => fn_def_node = Some(cap.node),
2307 "method.name" => method_name_node = Some(cap.node),
2308 "method.def" => method_def_node = Some(cap.node),
2309 "qual.scope" => qual_scope_node = Some(cap.node),
2310 "qual.name" => qual_name_node = Some(cap.node),
2311 "qual.def" => qual_def_node = Some(cap.node),
2312 "class.name" => class_name_node = Some(cap.node),
2313 "class.def" => class_def_node = Some(cap.node),
2314 "struct.name" => struct_name_node = Some(cap.node),
2315 "struct.def" => struct_def_node = Some(cap.node),
2316 "enum.name" => enum_name_node = Some(cap.node),
2317 "enum.def" => enum_def_node = Some(cap.node),
2318 "namespace.name" => namespace_name_node = Some(cap.node),
2319 "namespace.def" => namespace_def_node = Some(cap.node),
2320 "template.class.name" => template_class_name_node = Some(cap.node),
2321 "template.class.def" => template_class_def_node = Some(cap.node),
2322 "template.class.item" => template_class_item_node = Some(cap.node),
2323 "template.struct.name" => template_struct_name_node = Some(cap.node),
2324 "template.struct.def" => template_struct_def_node = Some(cap.node),
2325 "template.struct.item" => template_struct_item_node = Some(cap.node),
2326 "template.fn.name" => template_fn_name_node = Some(cap.node),
2327 "template.fn.def" => template_fn_def_node = Some(cap.node),
2328 "template.fn.item" => template_fn_item_node = Some(cap.node),
2329 "template.qual.scope" => template_qual_scope_node = Some(cap.node),
2330 "template.qual.name" => template_qual_name_node = Some(cap.node),
2331 "template.qual.def" => template_qual_def_node = Some(cap.node),
2332 "template.qual.item" => template_qual_item_node = Some(cap.node),
2333 _ => {}
2334 }
2335 }
2336
2337 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2338 let in_template = def_node
2339 .parent()
2340 .map(|parent| parent.kind() == "template_declaration")
2341 .unwrap_or(false);
2342 if !in_template {
2343 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2344 symbols.push(Symbol {
2345 name: node_text(source, &name_node).to_string(),
2346 kind: SymbolKind::Function,
2347 range: node_range_with_decorators(&def_node, source, lang),
2348 signature: Some(extract_signature(source, &def_node)),
2349 scope_chain: scope_chain.clone(),
2350 exported: false,
2351 parent: scope_chain.last().cloned(),
2352 });
2353 }
2354 }
2355
2356 if let (Some(name_node), Some(def_node)) = (method_name_node, method_def_node) {
2357 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2358 symbols.push(Symbol {
2359 name: node_text(source, &name_node).to_string(),
2360 kind: SymbolKind::Method,
2361 range: node_range_with_decorators(&def_node, source, lang),
2362 signature: Some(extract_signature(source, &def_node)),
2363 scope_chain: scope_chain.clone(),
2364 exported: false,
2365 parent: scope_chain.last().cloned(),
2366 });
2367 }
2368
2369 if let (Some(scope_node), Some(name_node), Some(def_node)) =
2370 (qual_scope_node, qual_name_node, qual_def_node)
2371 {
2372 let in_template = def_node
2373 .parent()
2374 .map(|parent| parent.kind() == "template_declaration")
2375 .unwrap_or(false);
2376 if !in_template {
2377 let scope_text = node_text(source, &scope_node);
2378 let scope_chain = split_scope_text(scope_text, "::");
2379 let parent = scope_chain.last().cloned();
2380 let kind = if parent
2381 .as_ref()
2382 .map(|segment| type_names.contains(segment))
2383 .unwrap_or(false)
2384 {
2385 SymbolKind::Method
2386 } else {
2387 SymbolKind::Function
2388 };
2389
2390 symbols.push(Symbol {
2391 name: node_text(source, &name_node).to_string(),
2392 kind,
2393 range: node_range_with_decorators(&def_node, source, lang),
2394 signature: Some(extract_signature(source, &def_node)),
2395 scope_chain,
2396 exported: false,
2397 parent,
2398 });
2399 }
2400 }
2401
2402 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
2403 let in_template = def_node
2404 .parent()
2405 .map(|parent| parent.kind() == "template_declaration")
2406 .unwrap_or(false);
2407 if !in_template {
2408 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2409 let name = last_scope_segment(node_text(source, &name_node), "::");
2410 symbols.push(Symbol {
2411 name: name.clone(),
2412 kind: SymbolKind::Class,
2413 range: node_range_with_decorators(&def_node, source, lang),
2414 signature: Some(extract_signature(source, &def_node)),
2415 scope_chain: scope_chain.clone(),
2416 exported: false,
2417 parent: scope_chain.last().cloned(),
2418 });
2419 }
2420 }
2421
2422 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
2423 let in_template = def_node
2424 .parent()
2425 .map(|parent| parent.kind() == "template_declaration")
2426 .unwrap_or(false);
2427 if !in_template {
2428 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2429 let name = last_scope_segment(node_text(source, &name_node), "::");
2430 symbols.push(Symbol {
2431 name: name.clone(),
2432 kind: SymbolKind::Struct,
2433 range: node_range_with_decorators(&def_node, source, lang),
2434 signature: Some(extract_signature(source, &def_node)),
2435 scope_chain: scope_chain.clone(),
2436 exported: false,
2437 parent: scope_chain.last().cloned(),
2438 });
2439 }
2440 }
2441
2442 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
2443 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2444 let name = last_scope_segment(node_text(source, &name_node), "::");
2445 symbols.push(Symbol {
2446 name: name.clone(),
2447 kind: SymbolKind::Enum,
2448 range: node_range_with_decorators(&def_node, source, lang),
2449 signature: Some(extract_signature(source, &def_node)),
2450 scope_chain: scope_chain.clone(),
2451 exported: false,
2452 parent: scope_chain.last().cloned(),
2453 });
2454 }
2455
2456 if let (Some(name_node), Some(def_node)) = (namespace_name_node, namespace_def_node) {
2457 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2458 symbols.push(Symbol {
2459 name: node_text(source, &name_node).to_string(),
2460 kind: SymbolKind::TypeAlias,
2461 range: node_range_with_decorators(&def_node, source, lang),
2462 signature: Some(extract_signature(source, &def_node)),
2463 scope_chain: scope_chain.clone(),
2464 exported: false,
2465 parent: scope_chain.last().cloned(),
2466 });
2467 }
2468
2469 if let (Some(name_node), Some(def_node), Some(item_node)) = (
2470 template_class_name_node,
2471 template_class_def_node,
2472 template_class_item_node,
2473 ) {
2474 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2475 let name = last_scope_segment(node_text(source, &name_node), "::");
2476 symbols.push(Symbol {
2477 name: name.clone(),
2478 kind: SymbolKind::Class,
2479 range: node_range_with_decorators(&def_node, source, lang),
2480 signature: Some(template_signature(source, &def_node, &item_node)),
2481 scope_chain: scope_chain.clone(),
2482 exported: false,
2483 parent: scope_chain.last().cloned(),
2484 });
2485 }
2486
2487 if let (Some(name_node), Some(def_node), Some(item_node)) = (
2488 template_struct_name_node,
2489 template_struct_def_node,
2490 template_struct_item_node,
2491 ) {
2492 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2493 let name = last_scope_segment(node_text(source, &name_node), "::");
2494 symbols.push(Symbol {
2495 name: name.clone(),
2496 kind: SymbolKind::Struct,
2497 range: node_range_with_decorators(&def_node, source, lang),
2498 signature: Some(template_signature(source, &def_node, &item_node)),
2499 scope_chain: scope_chain.clone(),
2500 exported: false,
2501 parent: scope_chain.last().cloned(),
2502 });
2503 }
2504
2505 if let (Some(name_node), Some(def_node), Some(item_node)) = (
2506 template_fn_name_node,
2507 template_fn_def_node,
2508 template_fn_item_node,
2509 ) {
2510 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2511 symbols.push(Symbol {
2512 name: node_text(source, &name_node).to_string(),
2513 kind: SymbolKind::Function,
2514 range: node_range_with_decorators(&def_node, source, lang),
2515 signature: Some(template_signature(source, &def_node, &item_node)),
2516 scope_chain: scope_chain.clone(),
2517 exported: false,
2518 parent: scope_chain.last().cloned(),
2519 });
2520 }
2521
2522 if let (Some(scope_node), Some(name_node), Some(def_node), Some(item_node)) = (
2523 template_qual_scope_node,
2524 template_qual_name_node,
2525 template_qual_def_node,
2526 template_qual_item_node,
2527 ) {
2528 let scope_chain = split_scope_text(node_text(source, &scope_node), "::");
2529 let parent = scope_chain.last().cloned();
2530 let kind = if parent
2531 .as_ref()
2532 .map(|segment| type_names.contains(segment))
2533 .unwrap_or(false)
2534 {
2535 SymbolKind::Method
2536 } else {
2537 SymbolKind::Function
2538 };
2539
2540 symbols.push(Symbol {
2541 name: node_text(source, &name_node).to_string(),
2542 kind,
2543 range: node_range_with_decorators(&def_node, source, lang),
2544 signature: Some(template_signature(source, &def_node, &item_node)),
2545 scope_chain,
2546 exported: false,
2547 parent,
2548 });
2549 }
2550 }
2551
2552 dedup_symbols(&mut symbols);
2553 Ok(symbols)
2554}
2555
2556fn extract_zig_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2558 let lang = LangId::Zig;
2559 let capture_names = query.capture_names();
2560
2561 let mut symbols = Vec::new();
2562 let mut cursor = QueryCursor::new();
2563 let mut matches = cursor.matches(query, *root, source.as_bytes());
2564
2565 while let Some(m) = {
2566 matches.advance();
2567 matches.get()
2568 } {
2569 let mut fn_name_node = None;
2570 let mut fn_def_node = None;
2571 let mut struct_name_node = None;
2572 let mut struct_def_node = None;
2573 let mut enum_name_node = None;
2574 let mut enum_def_node = None;
2575 let mut union_name_node = None;
2576 let mut union_def_node = None;
2577 let mut const_name_node = None;
2578 let mut const_def_node = None;
2579 let mut test_name_node = None;
2580 let mut test_def_node = None;
2581
2582 for cap in m.captures {
2583 let Some(&name) = capture_names.get(cap.index as usize) else {
2584 continue;
2585 };
2586 match name {
2587 "fn.name" => fn_name_node = Some(cap.node),
2588 "fn.def" => fn_def_node = Some(cap.node),
2589 "struct.name" => struct_name_node = Some(cap.node),
2590 "struct.def" => struct_def_node = Some(cap.node),
2591 "enum.name" => enum_name_node = Some(cap.node),
2592 "enum.def" => enum_def_node = Some(cap.node),
2593 "union.name" => union_name_node = Some(cap.node),
2594 "union.def" => union_def_node = Some(cap.node),
2595 "const.name" => const_name_node = Some(cap.node),
2596 "const.def" => const_def_node = Some(cap.node),
2597 "test.name" => test_name_node = Some(cap.node),
2598 "test.def" => test_def_node = Some(cap.node),
2599 _ => {}
2600 }
2601 }
2602
2603 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2604 let scope_chain = zig_container_scope_chain(&def_node, source);
2605 let kind = if scope_chain.is_empty() {
2606 SymbolKind::Function
2607 } else {
2608 SymbolKind::Method
2609 };
2610 symbols.push(Symbol {
2611 name: node_text(source, &name_node).to_string(),
2612 kind,
2613 range: node_range_with_decorators(&def_node, source, lang),
2614 signature: Some(extract_signature(source, &def_node)),
2615 scope_chain: scope_chain.clone(),
2616 exported: false,
2617 parent: scope_chain.last().cloned(),
2618 });
2619 }
2620
2621 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
2622 symbols.push(Symbol {
2623 name: node_text(source, &name_node).to_string(),
2624 kind: SymbolKind::Struct,
2625 range: node_range_with_decorators(&def_node, source, lang),
2626 signature: Some(extract_signature(source, &def_node)),
2627 scope_chain: vec![],
2628 exported: false,
2629 parent: None,
2630 });
2631 }
2632
2633 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
2634 symbols.push(Symbol {
2635 name: node_text(source, &name_node).to_string(),
2636 kind: SymbolKind::Enum,
2637 range: node_range_with_decorators(&def_node, source, lang),
2638 signature: Some(extract_signature(source, &def_node)),
2639 scope_chain: vec![],
2640 exported: false,
2641 parent: None,
2642 });
2643 }
2644
2645 if let (Some(name_node), Some(def_node)) = (union_name_node, union_def_node) {
2646 symbols.push(Symbol {
2647 name: node_text(source, &name_node).to_string(),
2648 kind: SymbolKind::TypeAlias,
2649 range: node_range_with_decorators(&def_node, source, lang),
2650 signature: Some(extract_signature(source, &def_node)),
2651 scope_chain: vec![],
2652 exported: false,
2653 parent: None,
2654 });
2655 }
2656
2657 if let (Some(name_node), Some(def_node)) = (const_name_node, const_def_node) {
2658 let signature = extract_signature(source, &def_node);
2659 let is_container = signature.contains("= struct")
2660 || signature.contains("= enum")
2661 || signature.contains("= union")
2662 || signature.contains("= opaque");
2663 let is_const = signature.trim_start().starts_with("const ");
2664 let name = node_text(source, &name_node).to_string();
2665 let already_captured = symbols.iter().any(|symbol| symbol.name == name);
2666 if is_const && !is_container && !already_captured {
2667 symbols.push(Symbol {
2668 name,
2669 kind: SymbolKind::Variable,
2670 range: node_range_with_decorators(&def_node, source, lang),
2671 signature: Some(signature),
2672 scope_chain: vec![],
2673 exported: false,
2674 parent: None,
2675 });
2676 }
2677 }
2678
2679 if let (Some(name_node), Some(def_node)) = (test_name_node, test_def_node) {
2680 let scope_chain = zig_container_scope_chain(&def_node, source);
2681 symbols.push(Symbol {
2682 name: node_text(source, &name_node).trim_matches('"').to_string(),
2683 kind: SymbolKind::Function,
2684 range: node_range_with_decorators(&def_node, source, lang),
2685 signature: Some(extract_signature(source, &def_node)),
2686 scope_chain: scope_chain.clone(),
2687 exported: false,
2688 parent: scope_chain.last().cloned(),
2689 });
2690 }
2691 }
2692
2693 dedup_symbols(&mut symbols);
2694 Ok(symbols)
2695}
2696
2697fn extract_csharp_symbols(
2699 source: &str,
2700 root: &Node,
2701 query: &Query,
2702) -> Result<Vec<Symbol>, AftError> {
2703 let lang = LangId::CSharp;
2704 let capture_names = query.capture_names();
2705
2706 let mut symbols = Vec::new();
2707 let mut cursor = QueryCursor::new();
2708 let mut matches = cursor.matches(query, *root, source.as_bytes());
2709
2710 while let Some(m) = {
2711 matches.advance();
2712 matches.get()
2713 } {
2714 let mut class_name_node = None;
2715 let mut class_def_node = None;
2716 let mut interface_name_node = None;
2717 let mut interface_def_node = None;
2718 let mut struct_name_node = None;
2719 let mut struct_def_node = None;
2720 let mut enum_name_node = None;
2721 let mut enum_def_node = None;
2722 let mut method_name_node = None;
2723 let mut method_def_node = None;
2724 let mut property_name_node = None;
2725 let mut property_def_node = None;
2726 let mut namespace_name_node = None;
2727 let mut namespace_def_node = None;
2728
2729 for cap in m.captures {
2730 let Some(&name) = capture_names.get(cap.index as usize) else {
2731 continue;
2732 };
2733 match name {
2734 "class.name" => class_name_node = Some(cap.node),
2735 "class.def" => class_def_node = Some(cap.node),
2736 "interface.name" => interface_name_node = Some(cap.node),
2737 "interface.def" => interface_def_node = Some(cap.node),
2738 "struct.name" => struct_name_node = Some(cap.node),
2739 "struct.def" => struct_def_node = Some(cap.node),
2740 "enum.name" => enum_name_node = Some(cap.node),
2741 "enum.def" => enum_def_node = Some(cap.node),
2742 "method.name" => method_name_node = Some(cap.node),
2743 "method.def" => method_def_node = Some(cap.node),
2744 "property.name" => property_name_node = Some(cap.node),
2745 "property.def" => property_def_node = Some(cap.node),
2746 "namespace.name" => namespace_name_node = Some(cap.node),
2747 "namespace.def" => namespace_def_node = Some(cap.node),
2748 _ => {}
2749 }
2750 }
2751
2752 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
2753 let scope_chain = csharp_scope_chain(&def_node, source);
2754 symbols.push(Symbol {
2755 name: node_text(source, &name_node).to_string(),
2756 kind: SymbolKind::Class,
2757 range: node_range_with_decorators(&def_node, source, lang),
2758 signature: Some(extract_signature(source, &def_node)),
2759 scope_chain: scope_chain.clone(),
2760 exported: false,
2761 parent: scope_chain.last().cloned(),
2762 });
2763 }
2764
2765 if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
2766 let scope_chain = csharp_scope_chain(&def_node, source);
2767 symbols.push(Symbol {
2768 name: node_text(source, &name_node).to_string(),
2769 kind: SymbolKind::Interface,
2770 range: node_range_with_decorators(&def_node, source, lang),
2771 signature: Some(extract_signature(source, &def_node)),
2772 scope_chain: scope_chain.clone(),
2773 exported: false,
2774 parent: scope_chain.last().cloned(),
2775 });
2776 }
2777
2778 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
2779 let scope_chain = csharp_scope_chain(&def_node, source);
2780 symbols.push(Symbol {
2781 name: node_text(source, &name_node).to_string(),
2782 kind: SymbolKind::Struct,
2783 range: node_range_with_decorators(&def_node, source, lang),
2784 signature: Some(extract_signature(source, &def_node)),
2785 scope_chain: scope_chain.clone(),
2786 exported: false,
2787 parent: scope_chain.last().cloned(),
2788 });
2789 }
2790
2791 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
2792 let scope_chain = csharp_scope_chain(&def_node, source);
2793 symbols.push(Symbol {
2794 name: node_text(source, &name_node).to_string(),
2795 kind: SymbolKind::Enum,
2796 range: node_range_with_decorators(&def_node, source, lang),
2797 signature: Some(extract_signature(source, &def_node)),
2798 scope_chain: scope_chain.clone(),
2799 exported: false,
2800 parent: scope_chain.last().cloned(),
2801 });
2802 }
2803
2804 if let (Some(name_node), Some(def_node)) = (method_name_node, method_def_node) {
2805 let scope_chain = csharp_scope_chain(&def_node, source);
2806 symbols.push(Symbol {
2807 name: node_text(source, &name_node).to_string(),
2808 kind: SymbolKind::Method,
2809 range: node_range_with_decorators(&def_node, source, lang),
2810 signature: Some(extract_signature(source, &def_node)),
2811 scope_chain: scope_chain.clone(),
2812 exported: false,
2813 parent: scope_chain.last().cloned(),
2814 });
2815 }
2816
2817 if let (Some(name_node), Some(def_node)) = (property_name_node, property_def_node) {
2818 let scope_chain = csharp_scope_chain(&def_node, source);
2819 symbols.push(Symbol {
2820 name: node_text(source, &name_node).to_string(),
2821 kind: SymbolKind::Variable,
2822 range: node_range_with_decorators(&def_node, source, lang),
2823 signature: Some(extract_signature(source, &def_node)),
2824 scope_chain: scope_chain.clone(),
2825 exported: false,
2826 parent: scope_chain.last().cloned(),
2827 });
2828 }
2829
2830 if let (Some(name_node), Some(def_node)) = (namespace_name_node, namespace_def_node) {
2831 let scope_chain = csharp_scope_chain(&def_node, source);
2832 symbols.push(Symbol {
2833 name: node_text(source, &name_node).to_string(),
2834 kind: SymbolKind::TypeAlias,
2835 range: node_range_with_decorators(&def_node, source, lang),
2836 signature: Some(extract_signature(source, &def_node)),
2837 scope_chain: scope_chain.clone(),
2838 exported: false,
2839 parent: scope_chain.last().cloned(),
2840 });
2841 }
2842 }
2843
2844 dedup_symbols(&mut symbols);
2845 Ok(symbols)
2846}
2847
2848fn find_type_identifier_recursive(node: &Node, source: &str) -> Option<String> {
2850 if node.kind() == "type_identifier" {
2851 return Some(node_text(source, node).to_string());
2852 }
2853 let mut cursor = node.walk();
2854 if cursor.goto_first_child() {
2855 loop {
2856 if let Some(result) = find_type_identifier_recursive(&cursor.node(), source) {
2857 return Some(result);
2858 }
2859 if !cursor.goto_next_sibling() {
2860 break;
2861 }
2862 }
2863 }
2864 None
2865}
2866
2867fn extract_bash_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2871 let lang = LangId::Bash;
2872 let capture_names = query.capture_names();
2873
2874 let mut symbols = Vec::new();
2875 let mut cursor = QueryCursor::new();
2876 let mut matches = cursor.matches(query, *root, source.as_bytes());
2877
2878 while let Some(m) = {
2879 matches.advance();
2880 matches.get()
2881 } {
2882 let mut fn_name_node = None;
2883 let mut fn_def_node = None;
2884
2885 for cap in m.captures {
2886 let Some(&name) = capture_names.get(cap.index as usize) else {
2887 continue;
2888 };
2889 match name {
2890 "fn.name" => fn_name_node = Some(cap.node),
2891 "fn.def" => fn_def_node = Some(cap.node),
2892 _ => {}
2893 }
2894 }
2895
2896 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2897 symbols.push(Symbol {
2898 name: node_text(source, &name_node).to_string(),
2899 kind: SymbolKind::Function,
2900 range: node_range_with_decorators(&def_node, source, lang),
2901 signature: Some(extract_signature(source, &def_node)),
2902 scope_chain: vec![],
2903 exported: false,
2904 parent: None,
2905 });
2906 }
2907 }
2908
2909 Ok(symbols)
2910}
2911
2912fn solidity_scope_chain(node: &Node, source: &str) -> Vec<String> {
2915 let mut chain = Vec::new();
2916 let mut current = node.parent();
2917
2918 while let Some(parent) = current {
2919 match parent.kind() {
2920 "contract_declaration" | "library_declaration" | "interface_declaration" => {
2921 if let Some(name_node) = parent.child_by_field_name("name") {
2922 chain.push(node_text(source, &name_node).to_string());
2923 }
2924 }
2925 _ => {}
2926 }
2927 current = parent.parent();
2928 }
2929
2930 chain.reverse();
2931 chain
2932}
2933
2934fn extract_solidity_symbols(
2935 source: &str,
2936 root: &Node,
2937 query: &Query,
2938) -> Result<Vec<Symbol>, AftError> {
2939 let lang = LangId::Solidity;
2940 let capture_names = query.capture_names();
2941
2942 let mut symbols = Vec::new();
2943 let mut cursor = QueryCursor::new();
2944 let mut matches = cursor.matches(query, *root, source.as_bytes());
2945
2946 while let Some(m) = {
2947 matches.advance();
2948 matches.get()
2949 } {
2950 let mut contract_name_node = None;
2951 let mut contract_def_node = None;
2952 let mut library_name_node = None;
2953 let mut library_def_node = None;
2954 let mut interface_name_node = None;
2955 let mut interface_def_node = None;
2956 let mut fn_name_node = None;
2957 let mut fn_def_node = None;
2958 let mut modifier_name_node = None;
2959 let mut modifier_def_node = None;
2960 let mut constructor_def_node = None;
2961 let mut event_name_node = None;
2962 let mut event_def_node = None;
2963 let mut error_name_node = None;
2964 let mut error_def_node = None;
2965 let mut struct_name_node = None;
2966 let mut struct_def_node = None;
2967 let mut enum_name_node = None;
2968 let mut enum_def_node = None;
2969 let mut var_name_node = None;
2970 let mut var_def_node = None;
2971
2972 for cap in m.captures {
2973 let Some(&name) = capture_names.get(cap.index as usize) else {
2974 continue;
2975 };
2976 match name {
2977 "contract.name" => contract_name_node = Some(cap.node),
2978 "contract.def" => contract_def_node = Some(cap.node),
2979 "library.name" => library_name_node = Some(cap.node),
2980 "library.def" => library_def_node = Some(cap.node),
2981 "interface.name" => interface_name_node = Some(cap.node),
2982 "interface.def" => interface_def_node = Some(cap.node),
2983 "fn.name" => fn_name_node = Some(cap.node),
2984 "fn.def" => fn_def_node = Some(cap.node),
2985 "modifier.name" => modifier_name_node = Some(cap.node),
2986 "modifier.def" => modifier_def_node = Some(cap.node),
2987 "constructor.def" => constructor_def_node = Some(cap.node),
2988 "event.name" => event_name_node = Some(cap.node),
2989 "event.def" => event_def_node = Some(cap.node),
2990 "error.name" => error_name_node = Some(cap.node),
2991 "error.def" => error_def_node = Some(cap.node),
2992 "struct.name" => struct_name_node = Some(cap.node),
2993 "struct.def" => struct_def_node = Some(cap.node),
2994 "enum.name" => enum_name_node = Some(cap.node),
2995 "enum.def" => enum_def_node = Some(cap.node),
2996 "var.name" => var_name_node = Some(cap.node),
2997 "var.def" => var_def_node = Some(cap.node),
2998 _ => {}
2999 }
3000 }
3001
3002 if let (Some(name_node), Some(def_node)) = (contract_name_node, contract_def_node) {
3004 symbols.push(Symbol {
3005 name: node_text(source, &name_node).to_string(),
3006 kind: SymbolKind::Class,
3007 range: node_range_with_decorators(&def_node, source, lang),
3008 signature: Some(extract_signature(source, &def_node)),
3009 scope_chain: vec![],
3010 exported: true,
3011 parent: None,
3012 });
3013 }
3014
3015 if let (Some(name_node), Some(def_node)) = (library_name_node, library_def_node) {
3017 symbols.push(Symbol {
3018 name: node_text(source, &name_node).to_string(),
3019 kind: SymbolKind::Class,
3020 range: node_range_with_decorators(&def_node, source, lang),
3021 signature: Some(extract_signature(source, &def_node)),
3022 scope_chain: vec![],
3023 exported: true,
3024 parent: None,
3025 });
3026 }
3027
3028 if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
3030 symbols.push(Symbol {
3031 name: node_text(source, &name_node).to_string(),
3032 kind: SymbolKind::Interface,
3033 range: node_range_with_decorators(&def_node, source, lang),
3034 signature: Some(extract_signature(source, &def_node)),
3035 scope_chain: vec![],
3036 exported: true,
3037 parent: None,
3038 });
3039 }
3040
3041 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
3043 let scope_chain = solidity_scope_chain(&def_node, source);
3044 let kind = if scope_chain.is_empty() {
3045 SymbolKind::Function
3046 } else {
3047 SymbolKind::Method
3048 };
3049 symbols.push(Symbol {
3050 name: node_text(source, &name_node).to_string(),
3051 kind,
3052 range: node_range_with_decorators(&def_node, source, lang),
3053 signature: Some(extract_signature(source, &def_node)),
3054 parent: scope_chain.last().cloned(),
3055 scope_chain,
3056 exported: true,
3057 });
3058 }
3059
3060 if let (Some(name_node), Some(def_node)) = (modifier_name_node, modifier_def_node) {
3062 let scope_chain = solidity_scope_chain(&def_node, source);
3063 symbols.push(Symbol {
3064 name: node_text(source, &name_node).to_string(),
3065 kind: SymbolKind::Method,
3066 range: node_range_with_decorators(&def_node, source, lang),
3067 signature: Some(extract_signature(source, &def_node)),
3068 parent: scope_chain.last().cloned(),
3069 scope_chain,
3070 exported: true,
3071 });
3072 }
3073
3074 if let Some(def_node) = constructor_def_node {
3076 let scope_chain = solidity_scope_chain(&def_node, source);
3077 symbols.push(Symbol {
3078 name: "constructor".to_string(),
3079 kind: SymbolKind::Method,
3080 range: node_range_with_decorators(&def_node, source, lang),
3081 signature: Some(extract_signature(source, &def_node)),
3082 parent: scope_chain.last().cloned(),
3083 scope_chain,
3084 exported: true,
3085 });
3086 }
3087
3088 if let (Some(name_node), Some(def_node)) = (event_name_node, event_def_node) {
3090 let scope_chain = solidity_scope_chain(&def_node, source);
3091 symbols.push(Symbol {
3092 name: node_text(source, &name_node).to_string(),
3093 kind: SymbolKind::Function,
3094 range: node_range_with_decorators(&def_node, source, lang),
3095 signature: Some(extract_signature(source, &def_node)),
3096 parent: scope_chain.last().cloned(),
3097 scope_chain,
3098 exported: true,
3099 });
3100 }
3101
3102 if let (Some(name_node), Some(def_node)) = (error_name_node, error_def_node) {
3104 let scope_chain = solidity_scope_chain(&def_node, source);
3105 symbols.push(Symbol {
3106 name: node_text(source, &name_node).to_string(),
3107 kind: SymbolKind::TypeAlias,
3108 range: node_range_with_decorators(&def_node, source, lang),
3109 signature: Some(extract_signature(source, &def_node)),
3110 parent: scope_chain.last().cloned(),
3111 scope_chain,
3112 exported: true,
3113 });
3114 }
3115
3116 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
3118 let scope_chain = solidity_scope_chain(&def_node, source);
3119 symbols.push(Symbol {
3120 name: node_text(source, &name_node).to_string(),
3121 kind: SymbolKind::Struct,
3122 range: node_range_with_decorators(&def_node, source, lang),
3123 signature: Some(extract_signature(source, &def_node)),
3124 parent: scope_chain.last().cloned(),
3125 scope_chain,
3126 exported: true,
3127 });
3128 }
3129
3130 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
3132 let scope_chain = solidity_scope_chain(&def_node, source);
3133 symbols.push(Symbol {
3134 name: node_text(source, &name_node).to_string(),
3135 kind: SymbolKind::Enum,
3136 range: node_range_with_decorators(&def_node, source, lang),
3137 signature: Some(extract_signature(source, &def_node)),
3138 parent: scope_chain.last().cloned(),
3139 scope_chain,
3140 exported: true,
3141 });
3142 }
3143
3144 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
3146 let scope_chain = solidity_scope_chain(&def_node, source);
3147 symbols.push(Symbol {
3148 name: node_text(source, &name_node).to_string(),
3149 kind: SymbolKind::Variable,
3150 range: node_range_with_decorators(&def_node, source, lang),
3151 signature: Some(extract_signature(source, &def_node)),
3152 parent: scope_chain.last().cloned(),
3153 scope_chain,
3154 exported: true,
3155 });
3156 }
3157 }
3158
3159 dedup_symbols(&mut symbols);
3160 Ok(symbols)
3161}
3162
3163fn extract_json_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
3164 let Some(value) = root.named_child(0) else {
3165 return Ok(Vec::new());
3166 };
3167
3168 if value.kind() != "object" {
3169 return Ok(Vec::new());
3170 }
3171
3172 let mut symbols = Vec::new();
3173 let mut cursor = value.walk();
3174 for child in value.named_children(&mut cursor) {
3175 if child.kind() != "pair" {
3176 continue;
3177 }
3178 let Some(key_node) = child.child_by_field_name("key") else {
3179 continue;
3180 };
3181 let name = node_text(source, &key_node).trim_matches('"').to_string();
3182 if name.is_empty() {
3183 continue;
3184 }
3185 symbols.push(Symbol {
3186 name,
3187 kind: SymbolKind::Variable,
3188 range: node_range_with_decorators(&child, source, LangId::Json),
3189 signature: None,
3190 scope_chain: vec![],
3191 exported: false,
3192 parent: None,
3193 });
3194 }
3195
3196 Ok(symbols)
3197}
3198
3199fn scala_scope_chain(node: &Node, source: &str) -> Vec<String> {
3200 let mut chain = Vec::new();
3201 let mut current = node.parent();
3202
3203 while let Some(parent) = current {
3204 match parent.kind() {
3205 "class_definition" | "object_definition" | "enum_definition" | "trait_definition" => {
3206 if let Some(name_node) = parent.child_by_field_name("name") {
3207 chain.push(node_text(source, &name_node).to_string());
3208 }
3209 }
3210 _ => {}
3211 }
3212 current = parent.parent();
3213 }
3214
3215 chain.reverse();
3216 chain
3217}
3218
3219fn extract_scala_symbols(
3220 source: &str,
3221 root: &Node,
3222 query: &Query,
3223) -> Result<Vec<Symbol>, AftError> {
3224 let lang = LangId::Scala;
3225 let capture_names = query.capture_names();
3226
3227 let mut symbols = Vec::new();
3228 let mut cursor = QueryCursor::new();
3229 let mut matches = cursor.matches(query, *root, source.as_bytes());
3230
3231 while let Some(m) = {
3232 matches.advance();
3233 matches.get()
3234 } {
3235 let mut class_name_node = None;
3236 let mut class_def_node = None;
3237 let mut object_name_node = None;
3238 let mut object_def_node = None;
3239 let mut enum_name_node = None;
3240 let mut enum_def_node = None;
3241 let mut trait_name_node = None;
3242 let mut trait_def_node = None;
3243 let mut fn_name_node = None;
3244 let mut fn_def_node = None;
3245 let mut val_name_node = None;
3246 let mut val_def_node = None;
3247 let mut var_name_node = None;
3248 let mut var_def_node = None;
3249 let mut type_name_node = None;
3250 let mut type_def_node = None;
3251
3252 for cap in m.captures {
3253 let Some(&name) = capture_names.get(cap.index as usize) else {
3254 continue;
3255 };
3256 match name {
3257 "class.name" => class_name_node = Some(cap.node),
3258 "class.def" => class_def_node = Some(cap.node),
3259 "object.name" => object_name_node = Some(cap.node),
3260 "object.def" => object_def_node = Some(cap.node),
3261 "enum.name" => enum_name_node = Some(cap.node),
3262 "enum.def" => enum_def_node = Some(cap.node),
3263 "trait.name" => trait_name_node = Some(cap.node),
3264 "trait.def" => trait_def_node = Some(cap.node),
3265 "fn.name" => fn_name_node = Some(cap.node),
3266 "fn.def" => fn_def_node = Some(cap.node),
3267 "val.name" => val_name_node = Some(cap.node),
3268 "val.def" => val_def_node = Some(cap.node),
3269 "var.name" => var_name_node = Some(cap.node),
3270 "var.def" => var_def_node = Some(cap.node),
3271 "given.name" => val_name_node = Some(cap.node),
3272 "given.def" => val_def_node = Some(cap.node),
3273 "type.name" => type_name_node = Some(cap.node),
3274 "type.def" => type_def_node = Some(cap.node),
3275 _ => {}
3276 }
3277 }
3278
3279 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
3280 symbols.push(Symbol {
3281 name: node_text(source, &name_node).to_string(),
3282 kind: SymbolKind::Class,
3283 range: node_range_with_decorators(&def_node, source, lang),
3284 signature: Some(extract_signature(source, &def_node)),
3285 scope_chain: scala_scope_chain(&def_node, source),
3286 exported: true,
3287 parent: scala_scope_chain(&def_node, source).last().cloned(),
3288 });
3289 }
3290
3291 if let (Some(name_node), Some(def_node)) = (object_name_node, object_def_node) {
3292 symbols.push(Symbol {
3293 name: node_text(source, &name_node).to_string(),
3294 kind: SymbolKind::Class,
3295 range: node_range_with_decorators(&def_node, source, lang),
3296 signature: Some(extract_signature(source, &def_node)),
3297 scope_chain: scala_scope_chain(&def_node, source),
3298 exported: true,
3299 parent: scala_scope_chain(&def_node, source).last().cloned(),
3300 });
3301 }
3302
3303 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
3304 symbols.push(Symbol {
3305 name: node_text(source, &name_node).to_string(),
3306 kind: SymbolKind::Class,
3307 range: node_range_with_decorators(&def_node, source, lang),
3308 signature: Some(extract_signature(source, &def_node)),
3309 scope_chain: scala_scope_chain(&def_node, source),
3310 exported: true,
3311 parent: scala_scope_chain(&def_node, source).last().cloned(),
3312 });
3313 }
3314
3315 if let (Some(name_node), Some(def_node)) = (trait_name_node, trait_def_node) {
3316 symbols.push(Symbol {
3317 name: node_text(source, &name_node).to_string(),
3318 kind: SymbolKind::Interface,
3319 range: node_range_with_decorators(&def_node, source, lang),
3320 signature: Some(extract_signature(source, &def_node)),
3321 scope_chain: scala_scope_chain(&def_node, source),
3322 exported: true,
3323 parent: scala_scope_chain(&def_node, source).last().cloned(),
3324 });
3325 }
3326
3327 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
3328 let scope_chain = scala_scope_chain(&def_node, source);
3329 let kind = if scope_chain.is_empty() {
3330 SymbolKind::Function
3331 } else {
3332 SymbolKind::Method
3333 };
3334 symbols.push(Symbol {
3335 name: node_text(source, &name_node).to_string(),
3336 kind,
3337 range: node_range_with_decorators(&def_node, source, lang),
3338 signature: Some(extract_signature(source, &def_node)),
3339 parent: scope_chain.last().cloned(),
3340 scope_chain,
3341 exported: true,
3342 });
3343 }
3344
3345 if let (Some(name_node), Some(def_node)) = (val_name_node, val_def_node) {
3346 let scope_chain = scala_scope_chain(&def_node, source);
3347 symbols.push(Symbol {
3348 name: node_text(source, &name_node).to_string(),
3349 kind: SymbolKind::Variable,
3350 range: node_range_with_decorators(&def_node, source, lang),
3351 signature: Some(extract_signature(source, &def_node)),
3352 parent: scope_chain.last().cloned(),
3353 scope_chain,
3354 exported: true,
3355 });
3356 }
3357
3358 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
3359 let scope_chain = scala_scope_chain(&def_node, source);
3360 symbols.push(Symbol {
3361 name: node_text(source, &name_node).to_string(),
3362 kind: SymbolKind::Variable,
3363 range: node_range_with_decorators(&def_node, source, lang),
3364 signature: Some(extract_signature(source, &def_node)),
3365 parent: scope_chain.last().cloned(),
3366 scope_chain,
3367 exported: true,
3368 });
3369 }
3370
3371 if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
3372 let scope_chain = scala_scope_chain(&def_node, source);
3373 symbols.push(Symbol {
3374 name: node_text(source, &name_node).to_string(),
3375 kind: SymbolKind::TypeAlias,
3376 range: node_range_with_decorators(&def_node, source, lang),
3377 signature: Some(extract_signature(source, &def_node)),
3378 parent: scope_chain.last().cloned(),
3379 scope_chain,
3380 exported: true,
3381 });
3382 }
3383 }
3384
3385 dedup_symbols(&mut symbols);
3386 Ok(symbols)
3387}
3388
3389fn extract_vue_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
3390 let mut symbols = Vec::new();
3391 collect_vue_sections(source, root, &mut symbols, true);
3392 dedup_symbols(&mut symbols);
3393 Ok(symbols)
3394}
3395
3396fn collect_vue_sections(
3397 source: &str,
3398 node: &Node,
3399 symbols: &mut Vec<Symbol>,
3400 allow_sections: bool,
3401) {
3402 let mut cursor = node.walk();
3403 if !cursor.goto_first_child() {
3404 return;
3405 }
3406
3407 loop {
3408 let child = cursor.node();
3409 if let Some(section_name) = vue_section_name(&child) {
3410 if allow_sections {
3411 symbols.push(Symbol {
3412 name: section_name.to_string(),
3413 kind: SymbolKind::Heading,
3414 range: node_range(&child),
3415 signature: vue_opening_tag_signature(source, &child),
3416 scope_chain: vec![],
3417 exported: false,
3418 parent: None,
3419 });
3420 }
3421 } else {
3422 collect_vue_sections(
3423 source,
3424 &child,
3425 symbols,
3426 allow_sections && child.kind() == "document",
3427 );
3428 }
3429
3430 if !cursor.goto_next_sibling() {
3431 break;
3432 }
3433 }
3434}
3435
3436fn vue_section_name(node: &Node) -> Option<&'static str> {
3437 match node.kind() {
3438 "template_element" => Some("template"),
3439 "script_element" => Some("script"),
3440 "style_element" => Some("style"),
3441 _ => None,
3442 }
3443}
3444
3445fn vue_opening_tag_signature(source: &str, node: &Node) -> Option<String> {
3446 find_child_by_kind(*node, "start_tag")
3447 .or_else(|| find_child_by_kind(*node, "script_start_tag"))
3448 .or_else(|| find_child_by_kind(*node, "style_start_tag"))
3449 .or_else(|| find_child_by_kind(*node, "template_start_tag"))
3450 .map(|tag| node_text(source, &tag).trim().to_string())
3451}
3452
3453fn extract_html_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
3454 let mut headings: Vec<(u8, Symbol)> = Vec::new();
3455 collect_html_headings(source, root, &mut headings);
3456
3457 let total_lines = source.lines().count() as u32;
3458
3459 for i in 0..headings.len() {
3463 let level = headings[i].0;
3464 let section_end = headings[i + 1..]
3465 .iter()
3466 .find(|(l, _)| *l <= level)
3467 .map(|(_, s)| s.range.start_line.saturating_sub(1))
3468 .unwrap_or_else(|| total_lines.saturating_sub(1));
3469 headings[i].1.range.end_line = section_end;
3470 if section_end != headings[i].1.range.start_line {
3471 headings[i].1.range.end_col = 0;
3472 }
3473 }
3474
3475 let mut scope_stack: Vec<(u8, String)> = Vec::new(); for (level, symbol) in headings.iter_mut() {
3478 while scope_stack.last().is_some_and(|(l, _)| *l >= *level) {
3480 scope_stack.pop();
3481 }
3482 symbol.scope_chain = scope_stack.iter().map(|(_, name)| name.clone()).collect();
3483 symbol.parent = scope_stack.last().map(|(_, name)| name.clone());
3484 scope_stack.push((*level, symbol.name.clone()));
3485 }
3486
3487 Ok(headings.into_iter().map(|(_, s)| s).collect())
3488}
3489
3490fn collect_html_headings(source: &str, node: &Node, headings: &mut Vec<(u8, Symbol)>) {
3492 let mut cursor = node.walk();
3493 if !cursor.goto_first_child() {
3494 return;
3495 }
3496
3497 loop {
3498 let child = cursor.node();
3499 if child.kind() == "element" {
3500 if let Some(start_tag) = child
3502 .child_by_field_name("start_tag")
3503 .or_else(|| child.child(0).filter(|c| c.kind() == "start_tag"))
3504 {
3505 if let Some(tag_name_node) = start_tag
3506 .child_by_field_name("tag_name")
3507 .or_else(|| start_tag.child(1).filter(|c| c.kind() == "tag_name"))
3508 {
3509 let tag_name = node_text(source, &tag_name_node).to_lowercase();
3510 if let Some(level) = match tag_name.as_str() {
3511 "h1" => Some(1u8),
3512 "h2" => Some(2),
3513 "h3" => Some(3),
3514 "h4" => Some(4),
3515 "h5" => Some(5),
3516 "h6" => Some(6),
3517 _ => None,
3518 } {
3519 let text = extract_element_text(source, &child).trim().to_string();
3521 if !text.is_empty() {
3522 let range = node_range(&child);
3523 let signature = format!("<h{}> {}", level, text);
3524 headings.push((
3525 level,
3526 Symbol {
3527 name: text,
3528 kind: SymbolKind::Heading,
3529 range,
3530 signature: Some(signature),
3531 scope_chain: vec![], exported: false,
3533 parent: None, },
3535 ));
3536 }
3537 }
3538 }
3539 }
3540 collect_html_headings(source, &child, headings);
3542 } else {
3543 collect_html_headings(source, &child, headings);
3545 }
3546
3547 if !cursor.goto_next_sibling() {
3548 break;
3549 }
3550 }
3551}
3552
3553fn extract_element_text(source: &str, node: &Node) -> String {
3555 let mut text = String::new();
3556 let mut cursor = node.walk();
3557 if !cursor.goto_first_child() {
3558 return text;
3559 }
3560 loop {
3561 let child = cursor.node();
3562 match child.kind() {
3563 "text" => {
3564 text.push_str(node_text(source, &child));
3565 }
3566 "element" => {
3567 text.push_str(&extract_element_text(source, &child));
3569 }
3570 _ => {}
3571 }
3572 if !cursor.goto_next_sibling() {
3573 break;
3574 }
3575 }
3576 text
3577}
3578
3579fn extract_md_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
3583 let mut symbols = Vec::new();
3584 extract_md_sections(source, root, &mut symbols, &[]);
3585 Ok(symbols)
3586}
3587
3588fn extract_md_sections(
3590 source: &str,
3591 node: &Node,
3592 symbols: &mut Vec<Symbol>,
3593 scope_chain: &[String],
3594) {
3595 let mut cursor = node.walk();
3596 if !cursor.goto_first_child() {
3597 return;
3598 }
3599
3600 loop {
3601 let child = cursor.node();
3602 match child.kind() {
3603 "section" => {
3604 let mut section_cursor = child.walk();
3607 let mut heading_name = String::new();
3608 let mut heading_level: u8 = 0;
3609
3610 if section_cursor.goto_first_child() {
3611 loop {
3612 let section_child = section_cursor.node();
3613 if section_child.kind() == "atx_heading" {
3614 let mut h_cursor = section_child.walk();
3616 if h_cursor.goto_first_child() {
3617 loop {
3618 let h_child = h_cursor.node();
3619 let kind = h_child.kind();
3620 if kind.starts_with("atx_h") && kind.ends_with("_marker") {
3621 heading_level = kind
3623 .strip_prefix("atx_h")
3624 .and_then(|s| s.strip_suffix("_marker"))
3625 .and_then(|s| s.parse::<u8>().ok())
3626 .unwrap_or(1);
3627 } else if h_child.kind() == "inline" {
3628 heading_name =
3629 node_text(source, &h_child).trim().to_string();
3630 }
3631 if !h_cursor.goto_next_sibling() {
3632 break;
3633 }
3634 }
3635 }
3636 }
3637 if !section_cursor.goto_next_sibling() {
3638 break;
3639 }
3640 }
3641 }
3642
3643 if !heading_name.is_empty() {
3644 let range = node_range(&child);
3645 let signature = format!(
3646 "{} {}",
3647 "#".repeat((heading_level as usize).min(6)),
3648 heading_name
3649 );
3650
3651 symbols.push(Symbol {
3652 name: heading_name.clone(),
3653 kind: SymbolKind::Heading,
3654 range,
3655 signature: Some(signature),
3656 scope_chain: scope_chain.to_vec(),
3657 exported: false,
3658 parent: scope_chain.last().cloned(),
3659 });
3660
3661 let mut new_scope = scope_chain.to_vec();
3663 new_scope.push(heading_name);
3664 extract_md_sections(source, &child, symbols, &new_scope);
3665 }
3666 }
3667 _ => {}
3668 }
3669
3670 if !cursor.goto_next_sibling() {
3671 break;
3672 }
3673 }
3674}
3675
3676fn dedup_symbols(symbols: &mut Vec<Symbol>) {
3680 let mut seen = std::collections::HashSet::new();
3681 symbols.retain(|s| {
3682 let key = (s.name.clone(), format!("{:?}", s.kind), s.range.start_line);
3683 seen.insert(key)
3684 });
3685}
3686
3687pub struct TreeSitterProvider {
3690 parser: RefCell<FileParser>,
3691}
3692
3693#[derive(Debug, Clone)]
3694struct ReExportTarget {
3695 file: PathBuf,
3696 symbol_name: String,
3697}
3698
3699impl TreeSitterProvider {
3700 pub fn new() -> Self {
3702 Self::with_symbol_cache(Arc::new(RwLock::new(SymbolCache::new())))
3703 }
3704
3705 pub fn with_symbol_cache(symbol_cache: SharedSymbolCache) -> Self {
3707 Self {
3708 parser: RefCell::new(FileParser::with_symbol_cache(symbol_cache)),
3709 }
3710 }
3711
3712 pub fn symbol_cache_len(&self) -> usize {
3714 let parser = self.parser.borrow();
3715 parser.symbol_cache_len()
3716 }
3717
3718 pub fn symbol_cache(&self) -> SharedSymbolCache {
3720 let parser = self.parser.borrow();
3721 parser.symbol_cache()
3722 }
3723
3724 fn resolve_symbol_inner(
3725 &self,
3726 file: &Path,
3727 name: &str,
3728 depth: usize,
3729 visited: &mut HashSet<(PathBuf, String)>,
3730 ) -> Result<Vec<SymbolMatch>, AftError> {
3731 if depth > MAX_REEXPORT_DEPTH {
3732 return Ok(Vec::new());
3733 }
3734
3735 let canonical_file = std::fs::canonicalize(file).unwrap_or_else(|_| file.to_path_buf());
3736 if !visited.insert((canonical_file, name.to_string())) {
3737 return Ok(Vec::new());
3738 }
3739
3740 let symbols = self.parser.borrow_mut().extract_symbols(file)?;
3741 let local_matches = symbol_matches_in_file(file, &symbols, name);
3742 if !local_matches.is_empty() {
3743 return Ok(local_matches);
3744 }
3745
3746 if name == "default" {
3747 let default_matches = self.resolve_local_default_export(file, &symbols)?;
3748 if !default_matches.is_empty() {
3749 return Ok(default_matches);
3750 }
3751 }
3752
3753 let reexport_targets = self.collect_reexport_targets(file, name)?;
3754 let mut matches = Vec::new();
3755 let mut seen = HashSet::new();
3756 for target in reexport_targets {
3757 for resolved in
3758 self.resolve_symbol_inner(&target.file, &target.symbol_name, depth + 1, visited)?
3759 {
3760 let key = format!(
3761 "{}:{}:{}:{}:{}:{}",
3762 resolved.file,
3763 resolved.symbol.name,
3764 resolved.symbol.range.start_line,
3765 resolved.symbol.range.start_col,
3766 resolved.symbol.range.end_line,
3767 resolved.symbol.range.end_col
3768 );
3769 if seen.insert(key) {
3770 matches.push(resolved);
3771 }
3772 }
3773 }
3774
3775 Ok(matches)
3776 }
3777
3778 fn collect_reexport_targets(
3779 &self,
3780 file: &Path,
3781 requested_name: &str,
3782 ) -> Result<Vec<ReExportTarget>, AftError> {
3783 let (source, tree, lang) = self.read_parsed_file(file)?;
3784 if !matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
3785 return Ok(Vec::new());
3786 }
3787
3788 let mut targets = Vec::new();
3789 let root = tree.root_node();
3790 let from_dir = file.parent().unwrap_or_else(|| Path::new("."));
3791
3792 let mut cursor = root.walk();
3793 if !cursor.goto_first_child() {
3794 return Ok(targets);
3795 }
3796
3797 loop {
3798 let node = cursor.node();
3799 if node.kind() == "export_statement" {
3800 let Some(source_node) = node.child_by_field_name("source") else {
3801 if !cursor.goto_next_sibling() {
3802 break;
3803 }
3804 continue;
3805 };
3806
3807 let Some(module_path) = string_content(&source, &source_node) else {
3808 if !cursor.goto_next_sibling() {
3809 break;
3810 }
3811 continue;
3812 };
3813
3814 let Some(target_file) = resolve_module_path(from_dir, &module_path) else {
3815 if !cursor.goto_next_sibling() {
3816 break;
3817 }
3818 continue;
3819 };
3820
3821 if let Some(export_clause) = find_child_by_kind(node, "export_clause") {
3822 if let Some(symbol_name) =
3823 resolve_export_clause_name(&source, &export_clause, requested_name)
3824 {
3825 targets.push(ReExportTarget {
3826 file: target_file,
3827 symbol_name,
3828 });
3829 }
3830 } else if export_statement_has_wildcard(&source, &node) {
3831 targets.push(ReExportTarget {
3832 file: target_file,
3833 symbol_name: requested_name.to_string(),
3834 });
3835 }
3836 }
3837
3838 if !cursor.goto_next_sibling() {
3839 break;
3840 }
3841 }
3842
3843 Ok(targets)
3844 }
3845
3846 fn resolve_local_default_export(
3847 &self,
3848 file: &Path,
3849 symbols: &[Symbol],
3850 ) -> Result<Vec<SymbolMatch>, AftError> {
3851 let (source, tree, lang) = self.read_parsed_file(file)?;
3852 if !matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
3853 return Ok(Vec::new());
3854 }
3855
3856 let root = tree.root_node();
3857 let mut matches = Vec::new();
3858 let mut seen = HashSet::new();
3859
3860 let mut cursor = root.walk();
3861 if !cursor.goto_first_child() {
3862 return Ok(matches);
3863 }
3864
3865 loop {
3866 let node = cursor.node();
3867 if node.kind() == "export_statement"
3868 && node.child_by_field_name("source").is_none()
3869 && node_contains_token(&source, &node, "default")
3870 {
3871 if let Some(target_name) = default_export_target_name(&source, &node) {
3872 for symbol_match in symbol_matches_in_file(file, symbols, &target_name) {
3873 let key = format!(
3874 "{}:{}:{}:{}:{}:{}",
3875 symbol_match.file,
3876 symbol_match.symbol.name,
3877 symbol_match.symbol.range.start_line,
3878 symbol_match.symbol.range.start_col,
3879 symbol_match.symbol.range.end_line,
3880 symbol_match.symbol.range.end_col
3881 );
3882 if seen.insert(key) {
3883 matches.push(symbol_match);
3884 }
3885 }
3886 }
3887 }
3888
3889 if !cursor.goto_next_sibling() {
3890 break;
3891 }
3892 }
3893
3894 Ok(matches)
3895 }
3896
3897 fn read_parsed_file(&self, file: &Path) -> Result<(String, Tree, LangId), AftError> {
3898 let source = std::fs::read_to_string(file).map_err(|e| AftError::FileNotFound {
3899 path: format!("{}: {}", file.display(), e),
3900 })?;
3901 let (tree, lang) = {
3902 let mut parser = self.parser.borrow_mut();
3903 parser.parse_cloned(file)?
3904 };
3905 Ok((source, tree, lang))
3906 }
3907}
3908
3909fn symbol_matches_in_file(file: &Path, symbols: &[Symbol], name: &str) -> Vec<SymbolMatch> {
3910 symbols
3911 .iter()
3912 .filter(|symbol| symbol.name == name)
3913 .cloned()
3914 .map(|symbol| SymbolMatch {
3915 file: file.display().to_string(),
3916 symbol,
3917 })
3918 .collect()
3919}
3920
3921fn string_content(source: &str, node: &Node) -> Option<String> {
3922 let text = node_text(source, node);
3923 if text.len() < 2 {
3924 return None;
3925 }
3926
3927 Some(
3928 text.trim_start_matches(|c| c == '\'' || c == '"')
3929 .trim_end_matches(|c| c == '\'' || c == '"')
3930 .to_string(),
3931 )
3932}
3933
3934fn find_child_by_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
3935 let mut cursor = node.walk();
3936 if !cursor.goto_first_child() {
3937 return None;
3938 }
3939
3940 loop {
3941 let child = cursor.node();
3942 if child.kind() == kind {
3943 return Some(child);
3944 }
3945 if !cursor.goto_next_sibling() {
3946 break;
3947 }
3948 }
3949
3950 None
3951}
3952
3953fn resolve_export_clause_name(
3954 source: &str,
3955 export_clause: &Node,
3956 requested_name: &str,
3957) -> Option<String> {
3958 let mut cursor = export_clause.walk();
3959 if !cursor.goto_first_child() {
3960 return None;
3961 }
3962
3963 loop {
3964 let child = cursor.node();
3965 if child.kind() == "export_specifier" {
3966 let (source_name, exported_name) = export_specifier_names(source, &child)?;
3967 if exported_name == requested_name {
3968 return Some(source_name);
3969 }
3970 }
3971
3972 if !cursor.goto_next_sibling() {
3973 break;
3974 }
3975 }
3976
3977 None
3978}
3979
3980fn export_specifier_names(source: &str, specifier: &Node) -> Option<(String, String)> {
3981 let source_name = specifier
3982 .child_by_field_name("name")
3983 .map(|node| node_text(source, &node).to_string());
3984 let alias_name = specifier
3985 .child_by_field_name("alias")
3986 .map(|node| node_text(source, &node).to_string());
3987
3988 if let Some(source_name) = source_name {
3989 let exported_name = alias_name.unwrap_or_else(|| source_name.clone());
3990 return Some((source_name, exported_name));
3991 }
3992
3993 let mut names = Vec::new();
3994 let mut cursor = specifier.walk();
3995 if cursor.goto_first_child() {
3996 loop {
3997 let child = cursor.node();
3998 let child_text = node_text(source, &child).trim();
3999 if matches!(
4000 child.kind(),
4001 "identifier" | "type_identifier" | "property_identifier"
4002 ) || child_text == "default"
4003 {
4004 names.push(child_text.to_string());
4005 }
4006 if !cursor.goto_next_sibling() {
4007 break;
4008 }
4009 }
4010 }
4011
4012 match names.as_slice() {
4013 [name] => Some((name.clone(), name.clone())),
4014 [source_name, exported_name, ..] => Some((source_name.clone(), exported_name.clone())),
4015 _ => None,
4016 }
4017}
4018
4019fn export_statement_has_wildcard(source: &str, node: &Node) -> bool {
4020 let mut cursor = node.walk();
4021 if !cursor.goto_first_child() {
4022 return false;
4023 }
4024
4025 loop {
4026 if node_text(source, &cursor.node()).trim() == "*" {
4027 return true;
4028 }
4029 if !cursor.goto_next_sibling() {
4030 break;
4031 }
4032 }
4033
4034 false
4035}
4036
4037fn node_contains_token(source: &str, node: &Node, token: &str) -> bool {
4038 let mut cursor = node.walk();
4039 if !cursor.goto_first_child() {
4040 return false;
4041 }
4042
4043 loop {
4044 if node_text(source, &cursor.node()).trim() == token {
4045 return true;
4046 }
4047 if !cursor.goto_next_sibling() {
4048 break;
4049 }
4050 }
4051
4052 false
4053}
4054
4055fn default_export_target_name(source: &str, export_stmt: &Node) -> Option<String> {
4056 let mut cursor = export_stmt.walk();
4057 if !cursor.goto_first_child() {
4058 return None;
4059 }
4060
4061 loop {
4062 let child = cursor.node();
4063 match child.kind() {
4064 "function_declaration"
4065 | "class_declaration"
4066 | "interface_declaration"
4067 | "enum_declaration"
4068 | "type_alias_declaration"
4069 | "lexical_declaration" => {
4070 if let Some(name_node) = child.child_by_field_name("name") {
4071 return Some(node_text(source, &name_node).to_string());
4072 }
4073
4074 if child.kind() == "lexical_declaration" {
4075 let mut child_cursor = child.walk();
4076 if child_cursor.goto_first_child() {
4077 loop {
4078 let nested = child_cursor.node();
4079 if nested.kind() == "variable_declarator" {
4080 if let Some(name_node) = nested.child_by_field_name("name") {
4081 return Some(node_text(source, &name_node).to_string());
4082 }
4083 }
4084 if !child_cursor.goto_next_sibling() {
4085 break;
4086 }
4087 }
4088 }
4089 }
4090 }
4091 "identifier" | "type_identifier" => {
4092 let text = node_text(source, &child);
4093 if text != "export" && text != "default" {
4094 return Some(text.to_string());
4095 }
4096 }
4097 _ => {}
4098 }
4099
4100 if !cursor.goto_next_sibling() {
4101 break;
4102 }
4103 }
4104
4105 None
4106}
4107
4108impl crate::language::LanguageProvider for TreeSitterProvider {
4109 fn resolve_symbol(&self, file: &Path, name: &str) -> Result<Vec<SymbolMatch>, AftError> {
4110 let matches = self.resolve_symbol_inner(file, name, 0, &mut HashSet::new())?;
4111
4112 if matches.is_empty() {
4113 Err(AftError::SymbolNotFound {
4114 name: name.to_string(),
4115 file: file.display().to_string(),
4116 })
4117 } else {
4118 Ok(matches)
4119 }
4120 }
4121
4122 fn list_symbols(&self, file: &Path) -> Result<Vec<Symbol>, AftError> {
4123 self.parser.borrow_mut().extract_symbols(file)
4124 }
4125
4126 fn as_any(&self) -> &dyn std::any::Any {
4127 self
4128 }
4129}
4130
4131#[cfg(test)]
4132mod tests {
4133 use super::*;
4134 use crate::language::LanguageProvider;
4135 use crate::symbol_cache_disk;
4136 use std::path::PathBuf;
4137
4138 fn fixture_path(name: &str) -> PathBuf {
4139 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
4140 .join("tests")
4141 .join("fixtures")
4142 .join(name)
4143 }
4144
4145 fn test_symbol(name: &str) -> Symbol {
4146 Symbol {
4147 name: name.to_string(),
4148 kind: SymbolKind::Function,
4149 range: Range {
4150 start_line: 0,
4151 start_col: 0,
4152 end_line: 0,
4153 end_col: 10,
4154 },
4155 signature: Some(format!("fn {name}()")),
4156 scope_chain: Vec::new(),
4157 exported: true,
4158 parent: None,
4159 }
4160 }
4161
4162 #[test]
4163 fn symbol_cache_load_from_disk_round_trips_synthetic_entry() {
4164 let project = tempfile::tempdir().expect("create project dir");
4165 let storage = tempfile::tempdir().expect("create storage dir");
4166 let source = project.path().join("src/lib.rs");
4167 std::fs::create_dir_all(source.parent().expect("source parent"))
4168 .expect("create source dir");
4169 std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
4170 let mtime = std::fs::metadata(&source)
4171 .expect("stat source")
4172 .modified()
4173 .expect("source mtime");
4174 let content = std::fs::read(&source).expect("read source");
4175 let size = content.len() as u64;
4176 let hash = crate::cache_freshness::hash_bytes(&content);
4177
4178 let mut cache = SymbolCache::new();
4179 cache.set_project_root(project.path().to_path_buf());
4180 cache.insert(
4181 source.clone(),
4182 mtime,
4183 size,
4184 hash,
4185 vec![test_symbol("cached")],
4186 );
4187 symbol_cache_disk::write_to_disk(&cache, storage.path(), "unit-project")
4188 .expect("write symbol cache");
4189
4190 let mut restored = SymbolCache::new();
4191 let loaded = restored.load_from_disk(storage.path(), "unit-project", project.path());
4192 let symbols = restored.get(&source, mtime).expect("restored symbols");
4193
4194 assert_eq!(loaded, 1);
4195 assert_eq!(symbols.len(), 1);
4196 assert_eq!(symbols[0].name, "cached");
4197 }
4198
4199 #[test]
4200 fn symbol_cache_load_from_disk_drops_stale_synthetic_entry() {
4201 let project = tempfile::tempdir().expect("create project dir");
4202 let storage = tempfile::tempdir().expect("create storage dir");
4203 let source = project.path().join("src/lib.rs");
4204 std::fs::create_dir_all(source.parent().expect("source parent"))
4205 .expect("create source dir");
4206 std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
4207 let mtime = std::fs::metadata(&source)
4208 .expect("stat source")
4209 .modified()
4210 .expect("source mtime");
4211 let content = std::fs::read(&source).expect("read source");
4212 let size = content.len() as u64;
4213 let hash = crate::cache_freshness::hash_bytes(&content);
4214
4215 let mut cache = SymbolCache::new();
4216 cache.set_project_root(project.path().to_path_buf());
4217 cache.insert(
4218 source.clone(),
4219 mtime,
4220 size,
4221 hash,
4222 vec![test_symbol("cached")],
4223 );
4224 symbol_cache_disk::write_to_disk(&cache, storage.path(), "stale-unit-project")
4225 .expect("write symbol cache");
4226
4227 std::fs::write(&source, "pub fn cached() {}\npub fn fresh() {}\n").expect("change source");
4228
4229 let mut restored = SymbolCache::new();
4230 let loaded = restored.load_from_disk(storage.path(), "stale-unit-project", project.path());
4231
4232 assert_eq!(loaded, 0);
4233 assert_eq!(restored.len(), 0);
4234 }
4235
4236 #[test]
4237 fn stale_prewarm_generation_cannot_repopulate_symbol_cache_after_reset() {
4238 let project = tempfile::tempdir().expect("create project dir");
4239 let storage = tempfile::tempdir().expect("create storage dir");
4240 let source = project.path().join("src/lib.rs");
4241 std::fs::create_dir_all(source.parent().expect("source parent"))
4242 .expect("create source dir");
4243 std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
4244 let mtime = std::fs::metadata(&source)
4245 .expect("stat source")
4246 .modified()
4247 .expect("source mtime");
4248 let content = std::fs::read(&source).expect("read source");
4249 let size = content.len() as u64;
4250 let hash = crate::cache_freshness::hash_bytes(&content);
4251
4252 let mut disk_cache = SymbolCache::new();
4253 disk_cache.set_project_root(project.path().to_path_buf());
4254 disk_cache.insert(
4255 source.clone(),
4256 mtime,
4257 size,
4258 hash,
4259 vec![test_symbol("cached")],
4260 );
4261 symbol_cache_disk::write_to_disk(&disk_cache, storage.path(), "prewarm-reset")
4262 .expect("write symbol cache");
4263
4264 let shared = Arc::new(RwLock::new(SymbolCache::new()));
4265 let stale_generation = shared.write().unwrap().reset();
4266 let active_generation = shared.write().unwrap().reset();
4267 assert_ne!(stale_generation, active_generation);
4268
4269 {
4270 let mut cache = shared.write().unwrap();
4271 assert!(!cache
4272 .set_project_root_for_generation(stale_generation, project.path().to_path_buf()));
4273 assert_eq!(
4274 cache.load_from_disk_for_generation(
4275 stale_generation,
4276 storage.path(),
4277 "prewarm-reset",
4278 project.path()
4279 ),
4280 0
4281 );
4282 }
4283
4284 let mut stale_parser =
4285 FileParser::with_symbol_cache_generation(Arc::clone(&shared), Some(stale_generation));
4286 stale_parser
4287 .extract_symbols(&source)
4288 .expect("stale prewarm parses source but must not write cache");
4289
4290 let cache = shared.read().unwrap();
4291 assert_eq!(cache.generation(), active_generation);
4292 assert_eq!(cache.len(), 0);
4293 assert!(cache.project_root().is_none());
4294 assert!(!cache.contains_key(&source));
4295 }
4296
4297 #[test]
4300 fn detect_ts() {
4301 assert_eq!(
4302 detect_language(Path::new("foo.ts")),
4303 Some(LangId::TypeScript)
4304 );
4305 }
4306
4307 #[test]
4308 fn detect_tsx() {
4309 assert_eq!(detect_language(Path::new("foo.tsx")), Some(LangId::Tsx));
4310 }
4311
4312 #[test]
4313 fn detect_js() {
4314 assert_eq!(
4315 detect_language(Path::new("foo.js")),
4316 Some(LangId::JavaScript)
4317 );
4318 }
4319
4320 #[test]
4321 fn detect_jsx() {
4322 assert_eq!(
4323 detect_language(Path::new("foo.jsx")),
4324 Some(LangId::JavaScript)
4325 );
4326 }
4327
4328 #[test]
4329 fn detect_py() {
4330 assert_eq!(detect_language(Path::new("foo.py")), Some(LangId::Python));
4331 }
4332
4333 #[test]
4334 fn detect_rs() {
4335 assert_eq!(detect_language(Path::new("foo.rs")), Some(LangId::Rust));
4336 }
4337
4338 #[test]
4339 fn detect_go() {
4340 assert_eq!(detect_language(Path::new("foo.go")), Some(LangId::Go));
4341 }
4342
4343 #[test]
4344 fn detect_c() {
4345 assert_eq!(detect_language(Path::new("foo.c")), Some(LangId::C));
4346 }
4347
4348 #[test]
4349 fn detect_h() {
4350 assert_eq!(detect_language(Path::new("foo.h")), Some(LangId::C));
4351 }
4352
4353 #[test]
4354 fn detect_cc() {
4355 assert_eq!(detect_language(Path::new("foo.cc")), Some(LangId::Cpp));
4356 }
4357
4358 #[test]
4359 fn detect_cpp() {
4360 assert_eq!(detect_language(Path::new("foo.cpp")), Some(LangId::Cpp));
4361 }
4362
4363 #[test]
4364 fn detect_cxx() {
4365 assert_eq!(detect_language(Path::new("foo.cxx")), Some(LangId::Cpp));
4366 }
4367
4368 #[test]
4369 fn detect_hpp() {
4370 assert_eq!(detect_language(Path::new("foo.hpp")), Some(LangId::Cpp));
4371 }
4372
4373 #[test]
4374 fn detect_hh() {
4375 assert_eq!(detect_language(Path::new("foo.hh")), Some(LangId::Cpp));
4376 }
4377
4378 #[test]
4379 fn detect_zig() {
4380 assert_eq!(detect_language(Path::new("foo.zig")), Some(LangId::Zig));
4381 }
4382
4383 #[test]
4384 fn detect_cs() {
4385 assert_eq!(detect_language(Path::new("foo.cs")), Some(LangId::CSharp));
4386 }
4387
4388 #[test]
4389 fn detect_unknown_returns_none() {
4390 assert_eq!(detect_language(Path::new("foo.txt")), None);
4391 }
4392
4393 #[test]
4396 fn unsupported_extension_returns_invalid_request() {
4397 let path = fixture_path("sample.ts");
4399 let bad_path = path.with_extension("txt");
4400 std::fs::write(&bad_path, "hello").unwrap();
4402 let provider = TreeSitterProvider::new();
4403 let result = provider.list_symbols(&bad_path);
4404 std::fs::remove_file(&bad_path).ok();
4405 match result {
4406 Err(AftError::InvalidRequest { message }) => {
4407 assert!(
4408 message.contains("unsupported file extension"),
4409 "msg: {}",
4410 message
4411 );
4412 assert!(message.contains("txt"), "msg: {}", message);
4413 }
4414 other => panic!("expected InvalidRequest, got {:?}", other),
4415 }
4416 }
4417
4418 #[test]
4421 fn ts_extracts_all_symbol_kinds() {
4422 let provider = TreeSitterProvider::new();
4423 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
4424
4425 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
4426 assert!(
4427 names.contains(&"greet"),
4428 "missing function greet: {:?}",
4429 names
4430 );
4431 assert!(names.contains(&"add"), "missing arrow fn add: {:?}", names);
4432 assert!(
4433 names.contains(&"UserService"),
4434 "missing class UserService: {:?}",
4435 names
4436 );
4437 assert!(
4438 names.contains(&"Config"),
4439 "missing interface Config: {:?}",
4440 names
4441 );
4442 assert!(
4443 names.contains(&"Status"),
4444 "missing enum Status: {:?}",
4445 names
4446 );
4447 assert!(
4448 names.contains(&"UserId"),
4449 "missing type alias UserId: {:?}",
4450 names
4451 );
4452 assert!(
4453 names.contains(&"internalHelper"),
4454 "missing non-exported fn: {:?}",
4455 names
4456 );
4457
4458 assert!(
4460 symbols.len() >= 6,
4461 "expected ≥6 symbols, got {}: {:?}",
4462 symbols.len(),
4463 names
4464 );
4465 }
4466
4467 #[test]
4468 fn ts_symbol_kinds_correct() {
4469 let provider = TreeSitterProvider::new();
4470 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
4471
4472 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
4473
4474 assert_eq!(find("greet").kind, SymbolKind::Function);
4475 assert_eq!(find("add").kind, SymbolKind::Function); assert_eq!(find("UserService").kind, SymbolKind::Class);
4477 assert_eq!(find("Config").kind, SymbolKind::Interface);
4478 assert_eq!(find("Status").kind, SymbolKind::Enum);
4479 assert_eq!(find("UserId").kind, SymbolKind::TypeAlias);
4480 }
4481
4482 #[test]
4483 fn ts_export_detection() {
4484 let provider = TreeSitterProvider::new();
4485 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
4486
4487 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
4488
4489 assert!(find("greet").exported, "greet should be exported");
4490 assert!(find("add").exported, "add should be exported");
4491 assert!(
4492 find("UserService").exported,
4493 "UserService should be exported"
4494 );
4495 assert!(find("Config").exported, "Config should be exported");
4496 assert!(find("Status").exported, "Status should be exported");
4497 assert!(
4498 !find("internalHelper").exported,
4499 "internalHelper should not be exported"
4500 );
4501 }
4502
4503 #[test]
4504 fn ts_method_scope_chain() {
4505 let provider = TreeSitterProvider::new();
4506 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
4507
4508 let methods: Vec<&Symbol> = symbols
4509 .iter()
4510 .filter(|s| s.kind == SymbolKind::Method)
4511 .collect();
4512 assert!(!methods.is_empty(), "should have at least one method");
4513
4514 for method in &methods {
4515 assert_eq!(
4516 method.scope_chain,
4517 vec!["UserService"],
4518 "method {} should have UserService in scope chain",
4519 method.name
4520 );
4521 assert_eq!(method.parent.as_deref(), Some("UserService"));
4522 }
4523 }
4524
4525 #[test]
4526 fn ts_signatures_present() {
4527 let provider = TreeSitterProvider::new();
4528 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
4529
4530 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
4531
4532 let greet_sig = find("greet").signature.as_ref().unwrap();
4533 assert!(
4534 greet_sig.contains("greet"),
4535 "signature should contain function name: {}",
4536 greet_sig
4537 );
4538 }
4539
4540 #[test]
4541 fn ts_ranges_valid() {
4542 let provider = TreeSitterProvider::new();
4543 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
4544
4545 for s in &symbols {
4546 assert!(
4547 s.range.end_line >= s.range.start_line,
4548 "symbol {} has invalid range: {:?}",
4549 s.name,
4550 s.range
4551 );
4552 }
4553 }
4554
4555 #[test]
4558 fn js_extracts_core_symbols() {
4559 let provider = TreeSitterProvider::new();
4560 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
4561
4562 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
4563 assert!(
4564 names.contains(&"multiply"),
4565 "missing function multiply: {:?}",
4566 names
4567 );
4568 assert!(
4569 names.contains(&"divide"),
4570 "missing arrow fn divide: {:?}",
4571 names
4572 );
4573 assert!(
4574 names.contains(&"EventEmitter"),
4575 "missing class EventEmitter: {:?}",
4576 names
4577 );
4578 assert!(
4579 names.contains(&"main"),
4580 "missing default export fn main: {:?}",
4581 names
4582 );
4583
4584 assert!(
4585 symbols.len() >= 4,
4586 "expected ≥4 symbols, got {}: {:?}",
4587 symbols.len(),
4588 names
4589 );
4590 }
4591
4592 #[test]
4593 fn js_arrow_fn_correctly_named() {
4594 let provider = TreeSitterProvider::new();
4595 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
4596
4597 let divide = symbols.iter().find(|s| s.name == "divide").unwrap();
4598 assert_eq!(divide.kind, SymbolKind::Function);
4599 assert!(divide.exported, "divide should be exported");
4600
4601 let internal = symbols.iter().find(|s| s.name == "internalUtil").unwrap();
4602 assert_eq!(internal.kind, SymbolKind::Function);
4603 assert!(!internal.exported, "internalUtil should not be exported");
4604 }
4605
4606 #[test]
4607 fn js_method_scope_chain() {
4608 let provider = TreeSitterProvider::new();
4609 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
4610
4611 let methods: Vec<&Symbol> = symbols
4612 .iter()
4613 .filter(|s| s.kind == SymbolKind::Method)
4614 .collect();
4615
4616 for method in &methods {
4617 assert_eq!(
4618 method.scope_chain,
4619 vec!["EventEmitter"],
4620 "method {} should have EventEmitter in scope chain",
4621 method.name
4622 );
4623 }
4624 }
4625
4626 #[test]
4629 fn tsx_extracts_react_component() {
4630 let provider = TreeSitterProvider::new();
4631 let symbols = provider.list_symbols(&fixture_path("sample.tsx")).unwrap();
4632
4633 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
4634 assert!(
4635 names.contains(&"Button"),
4636 "missing React component Button: {:?}",
4637 names
4638 );
4639 assert!(
4640 names.contains(&"Counter"),
4641 "missing class Counter: {:?}",
4642 names
4643 );
4644 assert!(
4645 names.contains(&"formatLabel"),
4646 "missing function formatLabel: {:?}",
4647 names
4648 );
4649
4650 assert!(
4651 symbols.len() >= 2,
4652 "expected ≥2 symbols, got {}: {:?}",
4653 symbols.len(),
4654 names
4655 );
4656 }
4657
4658 #[test]
4659 fn tsx_jsx_doesnt_break_parser() {
4660 let provider = TreeSitterProvider::new();
4662 let result = provider.list_symbols(&fixture_path("sample.tsx"));
4663 assert!(
4664 result.is_ok(),
4665 "TSX parsing should succeed: {:?}",
4666 result.err()
4667 );
4668 }
4669
4670 #[test]
4673 fn resolve_symbol_finds_match() {
4674 let provider = TreeSitterProvider::new();
4675 let matches = provider
4676 .resolve_symbol(&fixture_path("sample.ts"), "greet")
4677 .unwrap();
4678 assert_eq!(matches.len(), 1);
4679 assert_eq!(matches[0].symbol.name, "greet");
4680 assert_eq!(matches[0].symbol.kind, SymbolKind::Function);
4681 }
4682
4683 #[test]
4684 fn resolve_symbol_not_found() {
4685 let provider = TreeSitterProvider::new();
4686 let result = provider.resolve_symbol(&fixture_path("sample.ts"), "nonexistent");
4687 assert!(matches!(result, Err(AftError::SymbolNotFound { .. })));
4688 }
4689
4690 #[test]
4691 fn resolve_symbol_follows_reexport_chains() {
4692 let dir = tempfile::tempdir().unwrap();
4693 let config = dir.path().join("config.ts");
4694 let barrel1 = dir.path().join("barrel1.ts");
4695 let barrel2 = dir.path().join("barrel2.ts");
4696 let barrel3 = dir.path().join("barrel3.ts");
4697 let index = dir.path().join("index.ts");
4698
4699 std::fs::write(
4700 &config,
4701 "export class Config {}\nexport default class DefaultConfig {}\n",
4702 )
4703 .unwrap();
4704 std::fs::write(
4705 &barrel1,
4706 "export { Config } from './config';\nexport { default as NamedDefault } from './config';\n",
4707 )
4708 .unwrap();
4709 std::fs::write(
4710 &barrel2,
4711 "export { Config as RenamedConfig } from './barrel1';\n",
4712 )
4713 .unwrap();
4714 std::fs::write(
4715 &barrel3,
4716 "export * from './barrel2';\nexport * from './barrel1';\n",
4717 )
4718 .unwrap();
4719 std::fs::write(
4720 &index,
4721 "export class Config {}\nexport { RenamedConfig as FinalConfig } from './barrel3';\nexport * from './barrel3';\n",
4722 )
4723 .unwrap();
4724
4725 let provider = TreeSitterProvider::new();
4726 let config_canon = std::fs::canonicalize(&config).unwrap();
4727
4728 let direct = provider.resolve_symbol(&barrel1, "Config").unwrap();
4729 assert_eq!(direct.len(), 1);
4730 assert_eq!(direct[0].symbol.name, "Config");
4731 assert_eq!(direct[0].file, config_canon.display().to_string());
4732
4733 let renamed = provider.resolve_symbol(&barrel2, "RenamedConfig").unwrap();
4734 assert_eq!(renamed.len(), 1);
4735 assert_eq!(renamed[0].symbol.name, "Config");
4736 assert_eq!(renamed[0].file, config_canon.display().to_string());
4737
4738 let wildcard_chain = provider.resolve_symbol(&index, "FinalConfig").unwrap();
4739 assert_eq!(wildcard_chain.len(), 1);
4740 assert_eq!(wildcard_chain[0].symbol.name, "Config");
4741 assert_eq!(wildcard_chain[0].file, config_canon.display().to_string());
4742
4743 let wildcard_default = provider.resolve_symbol(&index, "NamedDefault").unwrap();
4744 assert_eq!(wildcard_default.len(), 1);
4745 assert_eq!(wildcard_default[0].symbol.name, "DefaultConfig");
4746 assert_eq!(wildcard_default[0].file, config_canon.display().to_string());
4747
4748 let local = provider.resolve_symbol(&index, "Config").unwrap();
4749 assert_eq!(local.len(), 1);
4750 assert_eq!(local[0].symbol.name, "Config");
4751 assert_eq!(local[0].file, index.display().to_string());
4752 }
4753
4754 #[test]
4757 fn symbol_range_includes_rust_attributes() {
4758 let dir = tempfile::tempdir().unwrap();
4759 let path = dir.path().join("test_attrs.rs");
4760 std::fs::write(
4761 &path,
4762 "/// This is a doc comment\n#[test]\n#[cfg(test)]\nfn my_test_fn() {\n assert!(true);\n}\n",
4763 )
4764 .unwrap();
4765
4766 let provider = TreeSitterProvider::new();
4767 let matches = provider.resolve_symbol(&path, "my_test_fn").unwrap();
4768 assert_eq!(matches.len(), 1);
4769 assert_eq!(
4770 matches[0].symbol.range.start_line, 0,
4771 "symbol range should include preceding /// doc comment, got start_line={}",
4772 matches[0].symbol.range.start_line
4773 );
4774 }
4775
4776 #[test]
4777 fn symbol_range_includes_go_doc_comment() {
4778 let dir = tempfile::tempdir().unwrap();
4779 let path = dir.path().join("test_doc.go");
4780 std::fs::write(
4781 &path,
4782 "package main\n\n// MyFunc does something useful.\n// It has a multi-line doc.\nfunc MyFunc() {\n}\n",
4783 )
4784 .unwrap();
4785
4786 let provider = TreeSitterProvider::new();
4787 let matches = provider.resolve_symbol(&path, "MyFunc").unwrap();
4788 assert_eq!(matches.len(), 1);
4789 assert_eq!(
4790 matches[0].symbol.range.start_line, 2,
4791 "symbol range should include preceding doc comments, got start_line={}",
4792 matches[0].symbol.range.start_line
4793 );
4794 }
4795
4796 #[test]
4797 fn symbol_range_skips_unrelated_comments() {
4798 let dir = tempfile::tempdir().unwrap();
4799 let path = dir.path().join("test_gap.go");
4800 std::fs::write(
4801 &path,
4802 "package main\n\n// This is a standalone comment\n\nfunc Standalone() {\n}\n",
4803 )
4804 .unwrap();
4805
4806 let provider = TreeSitterProvider::new();
4807 let matches = provider.resolve_symbol(&path, "Standalone").unwrap();
4808 assert_eq!(matches.len(), 1);
4809 assert_eq!(
4810 matches[0].symbol.range.start_line, 4,
4811 "symbol range should NOT include comment separated by blank line, got start_line={}",
4812 matches[0].symbol.range.start_line
4813 );
4814 }
4815
4816 #[test]
4817 fn parse_cache_returns_same_tree() {
4818 let mut parser = FileParser::new();
4819 let path = fixture_path("sample.ts");
4820
4821 let (tree1, _) = parser.parse(&path).unwrap();
4822 let tree1_root = tree1.root_node().byte_range();
4823
4824 let (tree2, _) = parser.parse(&path).unwrap();
4825 let tree2_root = tree2.root_node().byte_range();
4826
4827 assert_eq!(tree1_root, tree2_root);
4829 }
4830
4831 #[test]
4832 fn extract_symbols_from_tree_matches_list_symbols() {
4833 let path = fixture_path("sample.rs");
4834 let source = std::fs::read_to_string(&path).unwrap();
4835
4836 let provider = TreeSitterProvider::new();
4837 let listed = provider.list_symbols(&path).unwrap();
4838
4839 let mut parser = FileParser::new();
4840 let (tree, lang) = parser.parse(&path).unwrap();
4841 let extracted = extract_symbols_from_tree(&source, tree, lang).unwrap();
4842
4843 assert_eq!(symbols_as_debug(&extracted), symbols_as_debug(&listed));
4844 }
4845
4846 fn symbols_as_debug(symbols: &[Symbol]) -> Vec<String> {
4847 symbols
4848 .iter()
4849 .map(|symbol| {
4850 format!(
4851 "{}|{:?}|{}:{}-{}:{}|{:?}|{:?}|{}|{:?}",
4852 symbol.name,
4853 symbol.kind,
4854 symbol.range.start_line,
4855 symbol.range.start_col,
4856 symbol.range.end_line,
4857 symbol.range.end_col,
4858 symbol.signature,
4859 symbol.scope_chain,
4860 symbol.exported,
4861 symbol.parent,
4862 )
4863 })
4864 .collect()
4865 }
4866
4867 #[test]
4870 fn py_extracts_all_symbols() {
4871 let provider = TreeSitterProvider::new();
4872 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
4873
4874 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
4875 assert!(
4876 names.contains(&"top_level_function"),
4877 "missing top_level_function: {:?}",
4878 names
4879 );
4880 assert!(names.contains(&"MyClass"), "missing MyClass: {:?}", names);
4881 assert!(
4882 names.contains(&"instance_method"),
4883 "missing method instance_method: {:?}",
4884 names
4885 );
4886 assert!(
4887 names.contains(&"decorated_function"),
4888 "missing decorated_function: {:?}",
4889 names
4890 );
4891
4892 assert!(
4894 symbols.len() >= 4,
4895 "expected ≥4 symbols, got {}: {:?}",
4896 symbols.len(),
4897 names
4898 );
4899 }
4900
4901 #[test]
4902 fn py_symbol_kinds_correct() {
4903 let provider = TreeSitterProvider::new();
4904 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
4905
4906 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
4907
4908 assert_eq!(find("top_level_function").kind, SymbolKind::Function);
4909 assert_eq!(find("MyClass").kind, SymbolKind::Class);
4910 assert_eq!(find("instance_method").kind, SymbolKind::Method);
4911 assert_eq!(find("decorated_function").kind, SymbolKind::Function);
4912 assert_eq!(find("OuterClass").kind, SymbolKind::Class);
4913 assert_eq!(find("InnerClass").kind, SymbolKind::Class);
4914 assert_eq!(find("inner_method").kind, SymbolKind::Method);
4915 assert_eq!(find("outer_method").kind, SymbolKind::Method);
4916 }
4917
4918 #[test]
4919 fn py_method_scope_chain() {
4920 let provider = TreeSitterProvider::new();
4921 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
4922
4923 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
4924
4925 assert_eq!(
4927 find("instance_method").scope_chain,
4928 vec!["MyClass"],
4929 "instance_method should have MyClass in scope chain"
4930 );
4931 assert_eq!(find("instance_method").parent.as_deref(), Some("MyClass"));
4932
4933 assert_eq!(
4935 find("inner_method").scope_chain,
4936 vec!["OuterClass", "InnerClass"],
4937 "inner_method should have nested scope chain"
4938 );
4939
4940 assert_eq!(
4942 find("InnerClass").scope_chain,
4943 vec!["OuterClass"],
4944 "InnerClass should have OuterClass in scope"
4945 );
4946
4947 assert!(
4949 find("top_level_function").scope_chain.is_empty(),
4950 "top-level function should have empty scope chain"
4951 );
4952 }
4953
4954 #[test]
4955 fn py_decorated_function_signature() {
4956 let provider = TreeSitterProvider::new();
4957 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
4958
4959 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
4960
4961 let sig = find("decorated_function").signature.as_ref().unwrap();
4962 assert!(
4963 sig.contains("@staticmethod"),
4964 "decorated function signature should include decorator: {}",
4965 sig
4966 );
4967 assert!(
4968 sig.contains("def decorated_function"),
4969 "signature should include function def: {}",
4970 sig
4971 );
4972 }
4973
4974 #[test]
4975 fn py_ranges_valid() {
4976 let provider = TreeSitterProvider::new();
4977 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
4978
4979 for s in &symbols {
4980 assert!(
4981 s.range.end_line >= s.range.start_line,
4982 "symbol {} has invalid range: {:?}",
4983 s.name,
4984 s.range
4985 );
4986 }
4987 }
4988
4989 #[test]
4992 fn rs_extracts_all_symbols() {
4993 let provider = TreeSitterProvider::new();
4994 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
4995
4996 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
4997 assert!(
4998 names.contains(&"public_function"),
4999 "missing public_function: {:?}",
5000 names
5001 );
5002 assert!(
5003 names.contains(&"private_function"),
5004 "missing private_function: {:?}",
5005 names
5006 );
5007 assert!(names.contains(&"MyStruct"), "missing MyStruct: {:?}", names);
5008 assert!(names.contains(&"Color"), "missing enum Color: {:?}", names);
5009 assert!(
5010 names.contains(&"Drawable"),
5011 "missing trait Drawable: {:?}",
5012 names
5013 );
5014 assert!(
5016 names.contains(&"new"),
5017 "missing impl method new: {:?}",
5018 names
5019 );
5020
5021 assert!(
5023 symbols.len() >= 6,
5024 "expected ≥6 symbols, got {}: {:?}",
5025 symbols.len(),
5026 names
5027 );
5028 }
5029
5030 #[test]
5031 fn rs_symbol_kinds_correct() {
5032 let provider = TreeSitterProvider::new();
5033 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
5034
5035 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
5036
5037 assert_eq!(find("public_function").kind, SymbolKind::Function);
5038 assert_eq!(find("private_function").kind, SymbolKind::Function);
5039 assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
5040 assert_eq!(find("Color").kind, SymbolKind::Enum);
5041 assert_eq!(find("Drawable").kind, SymbolKind::Interface); assert_eq!(find("new").kind, SymbolKind::Method);
5043 }
5044
5045 #[test]
5046 fn rs_pub_export_detection() {
5047 let provider = TreeSitterProvider::new();
5048 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
5049
5050 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
5051
5052 assert!(
5053 find("public_function").exported,
5054 "pub fn should be exported"
5055 );
5056 assert!(
5057 !find("private_function").exported,
5058 "non-pub fn should not be exported"
5059 );
5060 assert!(find("MyStruct").exported, "pub struct should be exported");
5061 assert!(find("Color").exported, "pub enum should be exported");
5062 assert!(find("Drawable").exported, "pub trait should be exported");
5063 assert!(
5064 find("new").exported,
5065 "pub fn inside impl should be exported"
5066 );
5067 assert!(
5068 !find("helper").exported,
5069 "non-pub fn inside impl should not be exported"
5070 );
5071 }
5072
5073 #[test]
5074 fn rs_impl_method_scope_chain() {
5075 let provider = TreeSitterProvider::new();
5076 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
5077
5078 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
5079
5080 assert_eq!(
5082 find("new").scope_chain,
5083 vec!["MyStruct"],
5084 "impl method should have type in scope chain"
5085 );
5086 assert_eq!(find("new").parent.as_deref(), Some("MyStruct"));
5087
5088 assert!(
5090 find("public_function").scope_chain.is_empty(),
5091 "free function should have empty scope chain"
5092 );
5093 }
5094
5095 #[test]
5096 fn rs_trait_impl_scope_chain() {
5097 let provider = TreeSitterProvider::new();
5098 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
5099
5100 let draw = symbols.iter().find(|s| s.name == "draw").unwrap();
5102 assert_eq!(
5103 draw.scope_chain,
5104 vec!["Drawable for MyStruct"],
5105 "trait impl method should have 'Trait for Type' scope"
5106 );
5107 assert_eq!(draw.parent.as_deref(), Some("MyStruct"));
5108 }
5109
5110 #[test]
5111 fn rs_ranges_valid() {
5112 let provider = TreeSitterProvider::new();
5113 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
5114
5115 for s in &symbols {
5116 assert!(
5117 s.range.end_line >= s.range.start_line,
5118 "symbol {} has invalid range: {:?}",
5119 s.name,
5120 s.range
5121 );
5122 }
5123 }
5124
5125 #[test]
5128 fn go_extracts_all_symbols() {
5129 let provider = TreeSitterProvider::new();
5130 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
5131
5132 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
5133 assert!(
5134 names.contains(&"ExportedFunction"),
5135 "missing ExportedFunction: {:?}",
5136 names
5137 );
5138 assert!(
5139 names.contains(&"unexportedFunction"),
5140 "missing unexportedFunction: {:?}",
5141 names
5142 );
5143 assert!(
5144 names.contains(&"MyStruct"),
5145 "missing struct MyStruct: {:?}",
5146 names
5147 );
5148 assert!(
5149 names.contains(&"Reader"),
5150 "missing interface Reader: {:?}",
5151 names
5152 );
5153 assert!(
5155 names.contains(&"String"),
5156 "missing receiver method String: {:?}",
5157 names
5158 );
5159
5160 assert!(
5162 symbols.len() >= 4,
5163 "expected ≥4 symbols, got {}: {:?}",
5164 symbols.len(),
5165 names
5166 );
5167 }
5168
5169 #[test]
5170 fn go_symbol_kinds_correct() {
5171 let provider = TreeSitterProvider::new();
5172 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
5173
5174 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
5175
5176 assert_eq!(find("ExportedFunction").kind, SymbolKind::Function);
5177 assert_eq!(find("unexportedFunction").kind, SymbolKind::Function);
5178 assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
5179 assert_eq!(find("Reader").kind, SymbolKind::Interface);
5180 assert_eq!(find("String").kind, SymbolKind::Method);
5181 assert_eq!(find("helper").kind, SymbolKind::Method);
5182 }
5183
5184 #[test]
5185 fn go_uppercase_export_detection() {
5186 let provider = TreeSitterProvider::new();
5187 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
5188
5189 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
5190
5191 assert!(
5192 find("ExportedFunction").exported,
5193 "ExportedFunction (uppercase) should be exported"
5194 );
5195 assert!(
5196 !find("unexportedFunction").exported,
5197 "unexportedFunction (lowercase) should not be exported"
5198 );
5199 assert!(
5200 find("MyStruct").exported,
5201 "MyStruct (uppercase) should be exported"
5202 );
5203 assert!(
5204 find("Reader").exported,
5205 "Reader (uppercase) should be exported"
5206 );
5207 assert!(
5208 find("String").exported,
5209 "String method (uppercase) should be exported"
5210 );
5211 assert!(
5212 !find("helper").exported,
5213 "helper method (lowercase) should not be exported"
5214 );
5215 }
5216
5217 #[test]
5218 fn go_receiver_method_scope_chain() {
5219 let provider = TreeSitterProvider::new();
5220 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
5221
5222 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
5223
5224 assert_eq!(
5226 find("String").scope_chain,
5227 vec!["MyStruct"],
5228 "receiver method should have type in scope chain"
5229 );
5230 assert_eq!(find("String").parent.as_deref(), Some("MyStruct"));
5231
5232 assert!(
5234 find("ExportedFunction").scope_chain.is_empty(),
5235 "regular function should have empty scope chain"
5236 );
5237 }
5238
5239 #[test]
5240 fn go_ranges_valid() {
5241 let provider = TreeSitterProvider::new();
5242 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
5243
5244 for s in &symbols {
5245 assert!(
5246 s.range.end_line >= s.range.start_line,
5247 "symbol {} has invalid range: {:?}",
5248 s.name,
5249 s.range
5250 );
5251 }
5252 }
5253
5254 #[test]
5257 fn cross_language_all_six_produce_symbols() {
5258 let provider = TreeSitterProvider::new();
5259
5260 let fixtures = [
5261 ("sample.ts", "TypeScript"),
5262 ("sample.tsx", "TSX"),
5263 ("sample.js", "JavaScript"),
5264 ("sample.py", "Python"),
5265 ("sample.rs", "Rust"),
5266 ("sample.go", "Go"),
5267 ];
5268
5269 for (fixture, lang) in &fixtures {
5270 let symbols = provider
5271 .list_symbols(&fixture_path(fixture))
5272 .unwrap_or_else(|e| panic!("{} ({}) failed: {:?}", lang, fixture, e));
5273 assert!(
5274 symbols.len() >= 2,
5275 "{} should produce ≥2 symbols, got {}: {:?}",
5276 lang,
5277 symbols.len(),
5278 symbols.iter().map(|s| &s.name).collect::<Vec<_>>()
5279 );
5280 }
5281 }
5282
5283 #[test]
5286 fn symbol_cache_returns_cached_results_on_second_call() {
5287 let dir = tempfile::tempdir().unwrap();
5288 let file = dir.path().join("test.rs");
5289 std::fs::write(&file, "pub fn hello() {}\npub fn world() {}").unwrap();
5290
5291 let mut parser = FileParser::new();
5292
5293 let symbols1 = parser.extract_symbols(&file).unwrap();
5294 assert_eq!(symbols1.len(), 2);
5295
5296 let symbols2 = parser.extract_symbols(&file).unwrap();
5298 assert_eq!(symbols2.len(), 2);
5299 assert_eq!(symbols1[0].name, symbols2[0].name);
5300
5301 assert!(parser.symbol_cache.read().unwrap().contains_key(&file));
5303 }
5304
5305 #[test]
5306 fn symbol_cache_invalidates_on_file_change() {
5307 let dir = tempfile::tempdir().unwrap();
5308 let file = dir.path().join("test.rs");
5309 std::fs::write(&file, "pub fn hello() {}").unwrap();
5310
5311 let mut parser = FileParser::new();
5312
5313 let symbols1 = parser.extract_symbols(&file).unwrap();
5314 assert_eq!(symbols1.len(), 1);
5315 assert_eq!(symbols1[0].name, "hello");
5316
5317 std::thread::sleep(std::time::Duration::from_millis(50));
5319
5320 std::fs::write(&file, "pub fn hello() {}\npub fn goodbye() {}").unwrap();
5322
5323 let symbols2 = parser.extract_symbols(&file).unwrap();
5325 assert_eq!(symbols2.len(), 2);
5326 assert!(symbols2.iter().any(|s| s.name == "goodbye"));
5327 }
5328
5329 #[test]
5330 fn symbol_cache_invalidate_method_clears_entry() {
5331 let dir = tempfile::tempdir().unwrap();
5332 let file = dir.path().join("test.rs");
5333 std::fs::write(&file, "pub fn hello() {}").unwrap();
5334
5335 let mut parser = FileParser::new();
5336 parser.extract_symbols(&file).unwrap();
5337 assert!(parser.symbol_cache.read().unwrap().contains_key(&file));
5338
5339 parser.invalidate_symbols(&file);
5340 assert!(!parser.symbol_cache.read().unwrap().contains_key(&file));
5341 assert!(!parser.cache.contains_key(&file));
5343 }
5344
5345 #[test]
5346 fn symbol_cache_works_for_multiple_languages() {
5347 let dir = tempfile::tempdir().unwrap();
5348 let rs_file = dir.path().join("lib.rs");
5349 let ts_file = dir.path().join("app.ts");
5350 let py_file = dir.path().join("main.py");
5351
5352 std::fs::write(&rs_file, "pub fn rust_fn() {}").unwrap();
5353 std::fs::write(&ts_file, "export function tsFn() {}").unwrap();
5354 std::fs::write(&py_file, "def py_fn():\n pass").unwrap();
5355
5356 let mut parser = FileParser::new();
5357
5358 let rs_syms = parser.extract_symbols(&rs_file).unwrap();
5359 let ts_syms = parser.extract_symbols(&ts_file).unwrap();
5360 let py_syms = parser.extract_symbols(&py_file).unwrap();
5361
5362 assert!(rs_syms.iter().any(|s| s.name == "rust_fn"));
5363 assert!(ts_syms.iter().any(|s| s.name == "tsFn"));
5364 assert!(py_syms.iter().any(|s| s.name == "py_fn"));
5365
5366 assert_eq!(parser.symbol_cache.read().unwrap().len(), 3);
5368
5369 let rs_syms2 = parser.extract_symbols(&rs_file).unwrap();
5371 assert_eq!(rs_syms.len(), rs_syms2.len());
5372 }
5373
5374 #[test]
5375 fn extract_json_symbols_top_level_keys() {
5376 let dir = tempfile::tempdir().unwrap();
5377 let file = dir.path().join("package.json");
5378 std::fs::write(&file, r#"{"name": "x", "version": "1"}"#).unwrap();
5379
5380 let mut parser = FileParser::new();
5381 let symbols = parser.extract_symbols(&file).unwrap();
5382
5383 assert_eq!(symbols.len(), 2);
5384 assert!(symbols
5385 .iter()
5386 .any(|s| s.name == "name" && s.kind == SymbolKind::Variable));
5387 assert!(symbols
5388 .iter()
5389 .any(|s| s.name == "version" && s.kind == SymbolKind::Variable));
5390 }
5391
5392 #[test]
5393 fn extract_json_symbols_root_array() {
5394 let dir = tempfile::tempdir().unwrap();
5395 let file = dir.path().join("array.json");
5396 std::fs::write(&file, "[1,2,3]").unwrap();
5397
5398 let mut parser = FileParser::new();
5399 let symbols = parser.extract_symbols(&file).unwrap();
5400
5401 assert_eq!(symbols.len(), 0);
5402 }
5403
5404 #[test]
5405 fn extract_json_symbols_no_recursion_into_nested() {
5406 let dir = tempfile::tempdir().unwrap();
5407 let file = dir.path().join("nested.json");
5408 std::fs::write(&file, r#"{"scripts": {"build": "tsc"}}"#).unwrap();
5409
5410 let mut parser = FileParser::new();
5411 let symbols = parser.extract_symbols(&file).unwrap();
5412
5413 assert_eq!(symbols.len(), 1);
5414 assert_eq!(symbols[0].name, "scripts");
5415 assert!(!symbols.iter().any(|s| s.name == "build"));
5416 }
5417
5418 #[test]
5419 fn extract_scala_symbols_object_and_method() {
5420 let dir = tempfile::tempdir().unwrap();
5421 let file = dir.path().join("Greeter.scala");
5422 std::fs::write(
5423 &file,
5424 "object Greeter {\n def hello(name: String): String = s\"hi $name\"\n}",
5425 )
5426 .unwrap();
5427
5428 let mut parser = FileParser::new();
5429 let symbols = parser.extract_symbols(&file).unwrap();
5430
5431 assert!(symbols
5432 .iter()
5433 .any(|s| s.name == "Greeter" && s.kind == SymbolKind::Class));
5434 assert!(symbols.iter().any(|s| s.name == "hello"
5435 && s.kind == SymbolKind::Method
5436 && s.scope_chain == vec!["Greeter".to_string()]));
5437 }
5438
5439 #[test]
5440 fn extract_scala_symbols_class_and_trait() {
5441 let dir = tempfile::tempdir().unwrap();
5442 let file = dir.path().join("Types.scala");
5443 std::fs::write(&file, "class Foo\ntrait Bar").unwrap();
5444
5445 let mut parser = FileParser::new();
5446 let symbols = parser.extract_symbols(&file).unwrap();
5447
5448 assert!(symbols
5449 .iter()
5450 .any(|s| s.name == "Foo" && s.kind == SymbolKind::Class));
5451 assert!(symbols
5452 .iter()
5453 .any(|s| s.name == "Bar" && s.kind == SymbolKind::Interface));
5454 }
5455}