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::callgraph::resolve_module_path;
11use crate::error::AftError;
12use crate::symbol_cache_disk;
13use crate::symbols::{Range, Symbol, SymbolKind, SymbolMatch};
14
15const MAX_REEXPORT_DEPTH: usize = 10;
16
17const TS_QUERY: &str = r#"
20;; function declarations
21(function_declaration
22 name: (identifier) @fn.name) @fn.def
23
24;; arrow functions assigned to const/let/var
25(lexical_declaration
26 (variable_declarator
27 name: (identifier) @arrow.name
28 value: (arrow_function) @arrow.body)) @arrow.def
29
30;; class declarations
31(class_declaration
32 name: (type_identifier) @class.name) @class.def
33
34;; method definitions inside classes
35(class_declaration
36 name: (type_identifier) @method.class_name
37 body: (class_body
38 (method_definition
39 name: (property_identifier) @method.name) @method.def))
40
41;; interface declarations
42(interface_declaration
43 name: (type_identifier) @interface.name) @interface.def
44
45;; enum declarations
46(enum_declaration
47 name: (identifier) @enum.name) @enum.def
48
49;; type alias declarations
50(type_alias_declaration
51 name: (type_identifier) @type_alias.name) @type_alias.def
52
53;; top-level const/let variable declarations
54(lexical_declaration
55 (variable_declarator
56 name: (identifier) @var.name)) @var.def
57
58;; export statement wrappers (top-level only)
59(export_statement) @export.stmt
60"#;
61
62const JS_QUERY: &str = r#"
63;; function declarations
64(function_declaration
65 name: (identifier) @fn.name) @fn.def
66
67;; arrow functions assigned to const/let/var
68(lexical_declaration
69 (variable_declarator
70 name: (identifier) @arrow.name
71 value: (arrow_function) @arrow.body)) @arrow.def
72
73;; class declarations
74(class_declaration
75 name: (identifier) @class.name) @class.def
76
77;; method definitions inside classes
78(class_declaration
79 name: (identifier) @method.class_name
80 body: (class_body
81 (method_definition
82 name: (property_identifier) @method.name) @method.def))
83
84;; top-level const/let variable declarations
85(lexical_declaration
86 (variable_declarator
87 name: (identifier) @var.name)) @var.def
88
89;; export statement wrappers (top-level only)
90(export_statement) @export.stmt
91"#;
92
93const PY_QUERY: &str = r#"
94;; function definitions (top-level and nested)
95(function_definition
96 name: (identifier) @fn.name) @fn.def
97
98;; class definitions
99(class_definition
100 name: (identifier) @class.name) @class.def
101
102;; decorated definitions (wraps function_definition or class_definition)
103(decorated_definition
104 (decorator) @dec.decorator) @dec.def
105"#;
106
107const RS_QUERY: &str = r#"
108;; free functions (with optional visibility)
109(function_item
110 name: (identifier) @fn.name) @fn.def
111
112;; struct items
113(struct_item
114 name: (type_identifier) @struct.name) @struct.def
115
116;; enum items
117(enum_item
118 name: (type_identifier) @enum.name) @enum.def
119
120;; trait items
121(trait_item
122 name: (type_identifier) @trait.name) @trait.def
123
124;; impl blocks — capture the whole block to find methods
125(impl_item) @impl.def
126
127;; visibility modifiers on any item
128(visibility_modifier) @vis.mod
129"#;
130
131const GO_QUERY: &str = r#"
132;; function declarations
133(function_declaration
134 name: (identifier) @fn.name) @fn.def
135
136;; method declarations (with receiver)
137(method_declaration
138 name: (field_identifier) @method.name) @method.def
139
140;; type declarations (struct and interface)
141(type_declaration
142 (type_spec
143 name: (type_identifier) @type.name
144 type: (_) @type.body)) @type.def
145"#;
146
147const C_QUERY: &str = r#"
148;; function definitions
149(function_definition
150 declarator: (function_declarator
151 declarator: (identifier) @fn.name)) @fn.def
152
153;; function declarations / prototypes
154(declaration
155 declarator: (function_declarator
156 declarator: (identifier) @fn.name)) @fn.def
157
158;; struct declarations
159(struct_specifier
160 name: (type_identifier) @struct.name
161 body: (field_declaration_list)) @struct.def
162
163;; enum declarations
164(enum_specifier
165 name: (type_identifier) @enum.name
166 body: (enumerator_list)) @enum.def
167
168;; typedef aliases
169(type_definition
170 declarator: (type_identifier) @type.name) @type.def
171
172;; macros
173(preproc_def
174 name: (identifier) @macro.name) @macro.def
175
176(preproc_function_def
177 name: (identifier) @macro.name) @macro.def
178"#;
179
180const CPP_QUERY: &str = r#"
181;; free function definitions
182(function_definition
183 declarator: (function_declarator
184 declarator: (identifier) @fn.name)) @fn.def
185
186;; free function declarations
187(declaration
188 declarator: (function_declarator
189 declarator: (identifier) @fn.name)) @fn.def
190
191;; inline method definitions / declarations inside class bodies
192(function_definition
193 declarator: (function_declarator
194 declarator: (field_identifier) @method.name)) @method.def
195
196(field_declaration
197 declarator: (function_declarator
198 declarator: (field_identifier) @method.name)) @method.def
199
200;; qualified functions / methods
201(function_definition
202 declarator: (function_declarator
203 declarator: (qualified_identifier
204 scope: (_) @qual.scope
205 name: (identifier) @qual.name))) @qual.def
206
207(declaration
208 declarator: (function_declarator
209 declarator: (qualified_identifier
210 scope: (_) @qual.scope
211 name: (identifier) @qual.name))) @qual.def
212
213;; class / struct / enum / namespace declarations
214(class_specifier
215 name: (_) @class.name) @class.def
216
217(struct_specifier
218 name: (_) @struct.name) @struct.def
219
220(enum_specifier
221 name: (_) @enum.name) @enum.def
222
223(namespace_definition
224 name: (_) @namespace.name) @namespace.def
225
226;; template declarations
227(template_declaration
228 (class_specifier
229 name: (_) @template.class.name) @template.class.item) @template.class.def
230
231(template_declaration
232 (struct_specifier
233 name: (_) @template.struct.name) @template.struct.item) @template.struct.def
234
235(template_declaration
236 (function_definition
237 declarator: (function_declarator
238 declarator: (identifier) @template.fn.name)) @template.fn.item) @template.fn.def
239
240(template_declaration
241 (function_definition
242 declarator: (function_declarator
243 declarator: (qualified_identifier
244 scope: (_) @template.qual.scope
245 name: (identifier) @template.qual.name))) @template.qual.item) @template.qual.def
246"#;
247
248const ZIG_QUERY: &str = r#"
249;; functions
250(function_declaration
251 name: (identifier) @fn.name) @fn.def
252
253;; container declarations bound to const names
254(variable_declaration
255 (identifier) @struct.name
256 "="
257 (struct_declaration) @struct.body) @struct.def
258
259(variable_declaration
260 (identifier) @enum.name
261 "="
262 (enum_declaration) @enum.body) @enum.def
263
264(variable_declaration
265 (identifier) @union.name
266 "="
267 (union_declaration) @union.body) @union.def
268
269;; const declarations
270(variable_declaration
271 (identifier) @const.name) @const.def
272
273;; tests
274(test_declaration
275 (string) @test.name) @test.def
276
277(test_declaration
278 (identifier) @test.name) @test.def
279"#;
280
281const CSHARP_QUERY: &str = r#"
282;; types
283(class_declaration
284 name: (identifier) @class.name) @class.def
285
286(interface_declaration
287 name: (identifier) @interface.name) @interface.def
288
289(struct_declaration
290 name: (identifier) @struct.name) @struct.def
291
292(enum_declaration
293 name: (identifier) @enum.name) @enum.def
294
295;; members
296(method_declaration
297 name: (identifier) @method.name) @method.def
298
299(property_declaration
300 name: (identifier) @property.name) @property.def
301
302;; namespaces
303(namespace_declaration
304 name: (_) @namespace.name) @namespace.def
305
306(file_scoped_namespace_declaration
307 name: (_) @namespace.name) @namespace.def
308"#;
309
310const BASH_QUERY: &str = r#"
313;; function definitions (both `function foo()` and `foo()` styles)
314(function_definition
315 name: (word) @fn.name) @fn.def
316"#;
317
318#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
320pub enum LangId {
321 TypeScript,
322 Tsx,
323 JavaScript,
324 Python,
325 Rust,
326 Go,
327 C,
328 Cpp,
329 Zig,
330 CSharp,
331 Bash,
332 Html,
333 Markdown,
334}
335
336pub fn detect_language(path: &Path) -> Option<LangId> {
338 let ext = path.extension()?.to_str()?;
339 match ext {
340 "ts" => Some(LangId::TypeScript),
341 "tsx" => Some(LangId::Tsx),
342 "js" | "jsx" => Some(LangId::JavaScript),
343 "py" => Some(LangId::Python),
344 "rs" => Some(LangId::Rust),
345 "go" => Some(LangId::Go),
346 "c" | "h" => Some(LangId::C),
347 "cc" | "cpp" | "cxx" | "hpp" | "hh" => Some(LangId::Cpp),
348 "zig" => Some(LangId::Zig),
349 "cs" => Some(LangId::CSharp),
350 "sh" | "bash" | "zsh" => Some(LangId::Bash),
351 "html" | "htm" => Some(LangId::Html),
352 "md" | "markdown" | "mdx" => Some(LangId::Markdown),
353 _ => None,
354 }
355}
356
357pub fn grammar_for(lang: LangId) -> Language {
359 match lang {
360 LangId::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
361 LangId::Tsx => tree_sitter_typescript::LANGUAGE_TSX.into(),
362 LangId::JavaScript => tree_sitter_javascript::LANGUAGE.into(),
363 LangId::Python => tree_sitter_python::LANGUAGE.into(),
364 LangId::Rust => tree_sitter_rust::LANGUAGE.into(),
365 LangId::Go => tree_sitter_go::LANGUAGE.into(),
366 LangId::C => tree_sitter_c::LANGUAGE.into(),
367 LangId::Cpp => tree_sitter_cpp::LANGUAGE.into(),
368 LangId::Zig => tree_sitter_zig::LANGUAGE.into(),
369 LangId::CSharp => tree_sitter_c_sharp::LANGUAGE.into(),
370 LangId::Bash => tree_sitter_bash::LANGUAGE.into(),
371 LangId::Html => tree_sitter_html::LANGUAGE.into(),
372 LangId::Markdown => tree_sitter_md::LANGUAGE.into(),
373 }
374}
375
376fn query_for(lang: LangId) -> Option<&'static str> {
378 match lang {
379 LangId::TypeScript | LangId::Tsx => Some(TS_QUERY),
380 LangId::JavaScript => Some(JS_QUERY),
381 LangId::Python => Some(PY_QUERY),
382 LangId::Rust => Some(RS_QUERY),
383 LangId::Go => Some(GO_QUERY),
384 LangId::C => Some(C_QUERY),
385 LangId::Cpp => Some(CPP_QUERY),
386 LangId::Zig => Some(ZIG_QUERY),
387 LangId::CSharp => Some(CSHARP_QUERY),
388 LangId::Bash => Some(BASH_QUERY),
389 LangId::Html => None, LangId::Markdown => None,
391 }
392}
393
394static TS_QUERY_CACHE: LazyLock<Result<Query, String>> =
395 LazyLock::new(|| compile_query(LangId::TypeScript));
396static TSX_QUERY_CACHE: LazyLock<Result<Query, String>> =
397 LazyLock::new(|| compile_query(LangId::Tsx));
398static JS_QUERY_CACHE: LazyLock<Result<Query, String>> =
399 LazyLock::new(|| compile_query(LangId::JavaScript));
400static PY_QUERY_CACHE: LazyLock<Result<Query, String>> =
401 LazyLock::new(|| compile_query(LangId::Python));
402static RS_QUERY_CACHE: LazyLock<Result<Query, String>> =
403 LazyLock::new(|| compile_query(LangId::Rust));
404static GO_QUERY_CACHE: LazyLock<Result<Query, String>> =
405 LazyLock::new(|| compile_query(LangId::Go));
406static C_QUERY_CACHE: LazyLock<Result<Query, String>> = LazyLock::new(|| compile_query(LangId::C));
407static CPP_QUERY_CACHE: LazyLock<Result<Query, String>> =
408 LazyLock::new(|| compile_query(LangId::Cpp));
409static ZIG_QUERY_CACHE: LazyLock<Result<Query, String>> =
410 LazyLock::new(|| compile_query(LangId::Zig));
411static CSHARP_QUERY_CACHE: LazyLock<Result<Query, String>> =
412 LazyLock::new(|| compile_query(LangId::CSharp));
413static BASH_QUERY_CACHE: LazyLock<Result<Query, String>> =
414 LazyLock::new(|| compile_query(LangId::Bash));
415
416fn compile_query(lang: LangId) -> Result<Query, String> {
417 let query_src = query_for(lang).ok_or_else(|| format!("missing query for {lang:?}"))?;
418 let grammar = grammar_for(lang);
419 Query::new(&grammar, query_src)
420 .map_err(|error| format!("query compile error for {lang:?}: {error}"))
421}
422
423fn cached_query_for(lang: LangId) -> Result<Option<&'static Query>, AftError> {
424 let query = match lang {
425 LangId::TypeScript => Some(&*TS_QUERY_CACHE),
426 LangId::Tsx => Some(&*TSX_QUERY_CACHE),
427 LangId::JavaScript => Some(&*JS_QUERY_CACHE),
428 LangId::Python => Some(&*PY_QUERY_CACHE),
429 LangId::Rust => Some(&*RS_QUERY_CACHE),
430 LangId::Go => Some(&*GO_QUERY_CACHE),
431 LangId::C => Some(&*C_QUERY_CACHE),
432 LangId::Cpp => Some(&*CPP_QUERY_CACHE),
433 LangId::Zig => Some(&*ZIG_QUERY_CACHE),
434 LangId::CSharp => Some(&*CSHARP_QUERY_CACHE),
435 LangId::Bash => Some(&*BASH_QUERY_CACHE),
436 LangId::Html | LangId::Markdown => None,
437 };
438
439 query
440 .map(|result| {
441 result.as_ref().map_err(|message| AftError::ParseError {
442 message: message.clone(),
443 })
444 })
445 .transpose()
446}
447
448struct CachedTree {
450 mtime: SystemTime,
451 tree: Tree,
452}
453
454#[derive(Clone)]
456struct CachedSymbols {
457 mtime: SystemTime,
458 symbols: Vec<Symbol>,
459}
460
461#[derive(Clone, Default)]
464pub struct SymbolCache {
465 entries: HashMap<PathBuf, CachedSymbols>,
466 generation: u64,
467 project_root: Option<PathBuf>,
468}
469
470pub type SharedSymbolCache = Arc<RwLock<SymbolCache>>;
471
472impl SymbolCache {
473 pub fn new() -> Self {
474 Self {
475 entries: HashMap::new(),
476 generation: 0,
477 project_root: None,
478 }
479 }
480
481 pub fn set_project_root(&mut self, project_root: PathBuf) {
483 self.project_root = Some(project_root);
484 }
485
486 pub fn set_project_root_for_generation(
489 &mut self,
490 generation: u64,
491 project_root: PathBuf,
492 ) -> bool {
493 if self.generation != generation {
494 return false;
495 }
496 self.set_project_root(project_root);
497 true
498 }
499
500 pub fn insert(&mut self, path: PathBuf, mtime: SystemTime, symbols: Vec<Symbol>) {
502 self.entries.insert(path, CachedSymbols { mtime, symbols });
503 }
504
505 pub fn insert_for_generation(
507 &mut self,
508 generation: u64,
509 path: PathBuf,
510 mtime: SystemTime,
511 symbols: Vec<Symbol>,
512 ) -> bool {
513 if self.generation != generation {
514 return false;
515 }
516 self.insert(path, mtime, symbols);
517 true
518 }
519
520 pub fn get(&self, path: &Path, mtime: SystemTime) -> Option<Vec<Symbol>> {
522 self.entries
523 .get(path)
524 .and_then(|cached| (cached.mtime == mtime).then(|| cached.symbols.clone()))
525 }
526
527 pub fn contains_path_with_mtime(&self, path: &Path, mtime: SystemTime) -> bool {
529 self.entries
530 .get(path)
531 .is_some_and(|cached| cached.mtime == mtime)
532 }
533
534 pub fn load_from_disk(&mut self, storage_dir: &Path, project_key: &str) -> usize {
536 let Some(cache) = symbol_cache_disk::read_from_disk(storage_dir, project_key) else {
537 return 0;
538 };
539
540 self.project_root = Some(cache.project_root.clone());
541 self.entries.clear();
542 let mut loaded = 0usize;
543
544 for entry in cache.entries {
545 let path = cache.project_root.join(&entry.relative_path);
546 let Ok(metadata) = std::fs::metadata(&path) else {
547 continue;
548 };
549 let Ok(current_mtime) = metadata.modified() else {
550 continue;
551 };
552 if metadata.len() != entry.size || current_mtime != entry.mtime {
553 continue;
554 }
555
556 self.entries.insert(
557 path,
558 CachedSymbols {
559 mtime: entry.mtime,
560 symbols: entry.symbols,
561 },
562 );
563 loaded += 1;
564 }
565
566 loaded
567 }
568
569 pub fn load_from_disk_for_generation(
572 &mut self,
573 generation: u64,
574 storage_dir: &Path,
575 project_key: &str,
576 ) -> usize {
577 if self.generation != generation {
578 return 0;
579 }
580 self.load_from_disk(storage_dir, project_key)
581 }
582
583 pub fn invalidate(&mut self, path: &Path) {
585 self.entries.remove(path);
586 }
587
588 pub fn reset(&mut self) -> u64 {
590 self.entries.clear();
591 self.project_root = None;
592 self.generation = self.generation.wrapping_add(1);
593 self.generation
594 }
595
596 pub fn generation(&self) -> u64 {
598 self.generation
599 }
600
601 pub fn contains_key(&self, path: &Path) -> bool {
603 self.entries.contains_key(path)
604 }
605
606 pub fn len(&self) -> usize {
608 self.entries.len()
609 }
610
611 pub(crate) fn project_root(&self) -> Option<PathBuf> {
612 self.project_root.clone()
613 }
614
615 pub(crate) fn disk_entries(&self) -> Vec<(&PathBuf, SystemTime, u64, &Vec<Symbol>)> {
616 self.entries
617 .iter()
618 .filter_map(|(path, cached)| {
619 if cached.symbols.is_empty() {
620 return None;
621 }
622 let size = std::fs::metadata(path).ok()?.len();
623 Some((path, cached.mtime, size, &cached.symbols))
624 })
625 .collect()
626 }
627}
628
629pub struct FileParser {
632 cache: HashMap<PathBuf, CachedTree>,
633 parsers: HashMap<LangId, Parser>,
634 symbol_cache: SharedSymbolCache,
635 symbol_cache_generation: Option<u64>,
636}
637
638impl FileParser {
639 pub fn new() -> Self {
641 Self::with_symbol_cache(Arc::new(RwLock::new(SymbolCache::new())))
642 }
643
644 pub fn with_symbol_cache(symbol_cache: SharedSymbolCache) -> Self {
646 Self::with_symbol_cache_generation(symbol_cache, None)
647 }
648
649 pub fn with_symbol_cache_generation(
651 symbol_cache: SharedSymbolCache,
652 symbol_cache_generation: Option<u64>,
653 ) -> Self {
654 Self {
655 cache: HashMap::new(),
656 parsers: HashMap::new(),
657 symbol_cache,
658 symbol_cache_generation,
659 }
660 }
661
662 fn parser_for(&mut self, lang: LangId) -> Result<&mut Parser, AftError> {
663 use std::collections::hash_map::Entry;
664
665 match self.parsers.entry(lang) {
666 Entry::Occupied(entry) => Ok(entry.into_mut()),
667 Entry::Vacant(entry) => {
668 let grammar = grammar_for(lang);
669 let mut parser = Parser::new();
670 parser.set_language(&grammar).map_err(|e| {
671 log::error!("grammar init failed for {:?}: {}", lang, e);
672 AftError::ParseError {
673 message: format!("grammar init failed for {:?}: {}", lang, e),
674 }
675 })?;
676 Ok(entry.insert(parser))
677 }
678 }
679 }
680
681 pub fn symbol_cache_len(&self) -> usize {
683 self.symbol_cache
684 .read()
685 .map(|cache| cache.len())
686 .unwrap_or(0)
687 }
688
689 pub fn symbol_cache(&self) -> SharedSymbolCache {
691 Arc::clone(&self.symbol_cache)
692 }
693
694 pub fn parse(&mut self, path: &Path) -> Result<(&Tree, LangId), AftError> {
697 let lang = detect_language(path).ok_or_else(|| AftError::InvalidRequest {
698 message: format!(
699 "unsupported file extension: {}",
700 path.extension()
701 .and_then(|e| e.to_str())
702 .unwrap_or("<none>")
703 ),
704 })?;
705
706 let canon = path.to_path_buf();
707 let current_mtime = std::fs::metadata(path)
708 .and_then(|m| m.modified())
709 .map_err(|e| AftError::FileNotFound {
710 path: format!("{}: {}", path.display(), e),
711 })?;
712
713 let needs_reparse = match self.cache.get(&canon) {
715 Some(cached) => cached.mtime != current_mtime,
716 None => true,
717 };
718
719 if needs_reparse {
720 let source = std::fs::read_to_string(path).map_err(|e| AftError::FileNotFound {
721 path: format!("{}: {}", path.display(), e),
722 })?;
723
724 let tree = self.parser_for(lang)?.parse(&source, None).ok_or_else(|| {
725 log::error!("parse failed for {}", path.display());
726 AftError::ParseError {
727 message: format!("tree-sitter parse returned None for {}", path.display()),
728 }
729 })?;
730
731 self.cache.insert(
732 canon.clone(),
733 CachedTree {
734 mtime: current_mtime,
735 tree,
736 },
737 );
738 }
739
740 let cached = self.cache.get(&canon).ok_or_else(|| AftError::ParseError {
741 message: format!("parser cache missing entry for {}", path.display()),
742 })?;
743 Ok((&cached.tree, lang))
744 }
745
746 pub fn parse_cloned(&mut self, path: &Path) -> Result<(Tree, LangId), AftError> {
751 let (tree, lang) = self.parse(path)?;
752 Ok((tree.clone(), lang))
753 }
754
755 pub fn extract_symbols(&mut self, path: &Path) -> Result<Vec<Symbol>, AftError> {
759 let canon = path.to_path_buf();
760 let current_mtime = std::fs::metadata(path)
761 .and_then(|m| m.modified())
762 .map_err(|e| AftError::FileNotFound {
763 path: format!("{}: {}", path.display(), e),
764 })?;
765
766 if let Some(symbols) = self
768 .symbol_cache
769 .read()
770 .map_err(|_| AftError::ParseError {
771 message: "symbol cache lock poisoned".to_string(),
772 })?
773 .get(&canon, current_mtime)
774 {
775 return Ok(symbols);
776 }
777
778 let source = std::fs::read_to_string(path).map_err(|e| AftError::FileNotFound {
779 path: format!("{}: {}", path.display(), e),
780 })?;
781
782 let symbols = {
783 let (tree, lang) = self.parse(path)?;
784 extract_symbols_from_tree(&source, tree, lang)?
785 };
786
787 let mut symbol_cache = self
789 .symbol_cache
790 .write()
791 .map_err(|_| AftError::ParseError {
792 message: "symbol cache lock poisoned".to_string(),
793 })?;
794 if let Some(generation) = self.symbol_cache_generation {
795 symbol_cache.insert_for_generation(generation, canon, current_mtime, symbols.clone());
796 } else {
797 symbol_cache.insert(canon, current_mtime, symbols.clone());
798 }
799
800 Ok(symbols)
801 }
802
803 pub fn invalidate_symbols(&mut self, path: &Path) {
805 if let Ok(mut symbol_cache) = self.symbol_cache.write() {
806 symbol_cache.invalidate(path);
807 }
808 self.cache.remove(path);
809 }
810}
811
812pub fn extract_symbols_from_tree(
817 source: &str,
818 tree: &Tree,
819 lang: LangId,
820) -> Result<Vec<Symbol>, AftError> {
821 let root = tree.root_node();
822
823 if lang == LangId::Html {
824 return extract_html_symbols(source, &root);
825 }
826 if lang == LangId::Markdown {
827 return extract_md_symbols(source, &root);
828 }
829
830 let query = cached_query_for(lang)?.ok_or_else(|| AftError::InvalidRequest {
831 message: format!("no query patterns implemented for {:?} yet", lang),
832 })?;
833
834 match lang {
835 LangId::TypeScript | LangId::Tsx => extract_ts_symbols(source, &root, query),
836 LangId::JavaScript => extract_js_symbols(source, &root, query),
837 LangId::Python => extract_py_symbols(source, &root, query),
838 LangId::Rust => extract_rs_symbols(source, &root, query),
839 LangId::Go => extract_go_symbols(source, &root, query),
840 LangId::C => extract_c_symbols(source, &root, query),
841 LangId::Cpp => extract_cpp_symbols(source, &root, query),
842 LangId::Zig => extract_zig_symbols(source, &root, query),
843 LangId::CSharp => extract_csharp_symbols(source, &root, query),
844 LangId::Bash => extract_bash_symbols(source, &root, query),
845 LangId::Html | LangId::Markdown => unreachable!("handled before query lookup"),
846 }
847}
848
849pub(crate) fn node_range(node: &Node) -> Range {
851 let start = node.start_position();
852 let end = node.end_position();
853 Range {
854 start_line: start.row as u32,
855 start_col: start.column as u32,
856 end_line: end.row as u32,
857 end_col: end.column as u32,
858 }
859}
860
861pub(crate) fn node_range_with_decorators(node: &Node, source: &str, lang: LangId) -> Range {
867 let mut range = node_range(node);
868
869 let mut current = *node;
870 while let Some(prev) = current.prev_sibling() {
871 let kind = prev.kind();
872 let should_include = match lang {
873 LangId::Rust => {
874 kind == "attribute_item"
876 || (kind == "line_comment"
878 && node_text(source, &prev).starts_with("///"))
879 || (kind == "block_comment"
881 && node_text(source, &prev).starts_with("/**"))
882 }
883 LangId::TypeScript | LangId::Tsx | LangId::JavaScript => {
884 kind == "decorator"
886 || (kind == "comment"
888 && node_text(source, &prev).starts_with("/**"))
889 }
890 LangId::Go | LangId::C | LangId::Cpp | LangId::Zig | LangId::CSharp | LangId::Bash => {
891 kind == "comment" && is_adjacent_line(&prev, ¤t, source)
893 }
894 LangId::Python => {
895 false
897 }
898 LangId::Html | LangId::Markdown => false,
899 };
900
901 if should_include {
902 range.start_line = prev.start_position().row as u32;
903 range.start_col = prev.start_position().column as u32;
904 current = prev;
905 } else {
906 break;
907 }
908 }
909
910 range
911}
912
913fn is_adjacent_line(upper: &Node, lower: &Node, source: &str) -> bool {
915 let upper_end = upper.end_position().row;
916 let lower_start = lower.start_position().row;
917
918 if lower_start == 0 || lower_start <= upper_end {
919 return true;
920 }
921
922 let lines: Vec<&str> = source.lines().collect();
924 for row in (upper_end + 1)..lower_start {
925 if row < lines.len() && lines[row].trim().is_empty() {
926 return false;
927 }
928 }
929 true
930}
931
932pub(crate) fn node_text<'a>(source: &'a str, node: &Node) -> &'a str {
934 &source[node.byte_range()]
935}
936
937fn lexical_declaration_has_function_value(node: &Node) -> bool {
938 let mut cursor = node.walk();
939 if !cursor.goto_first_child() {
940 return false;
941 }
942
943 loop {
944 let child = cursor.node();
945 if matches!(
946 child.kind(),
947 "arrow_function" | "function_expression" | "generator_function"
948 ) {
949 return true;
950 }
951
952 if lexical_declaration_has_function_value(&child) {
953 return true;
954 }
955
956 if !cursor.goto_next_sibling() {
957 break;
958 }
959 }
960
961 false
962}
963
964fn collect_export_ranges(source: &str, root: &Node, query: &Query) -> Vec<std::ops::Range<usize>> {
966 let export_idx = query
967 .capture_names()
968 .iter()
969 .position(|n| *n == "export.stmt");
970 let export_idx = match export_idx {
971 Some(i) => i as u32,
972 None => return vec![],
973 };
974
975 let mut cursor = QueryCursor::new();
976 let mut ranges = Vec::new();
977 let mut matches = cursor.matches(query, *root, source.as_bytes());
978
979 while let Some(m) = {
980 matches.advance();
981 matches.get()
982 } {
983 for cap in m.captures {
984 if cap.index == export_idx {
985 ranges.push(cap.node.byte_range());
986 }
987 }
988 }
989 ranges
990}
991
992fn is_exported(node: &Node, export_ranges: &[std::ops::Range<usize>]) -> bool {
994 let r = node.byte_range();
995 export_ranges
996 .iter()
997 .any(|er| er.start <= r.start && r.end <= er.end)
998}
999
1000fn extract_signature(source: &str, node: &Node) -> String {
1002 let text = node_text(source, node);
1003 let first_line = text.lines().next().unwrap_or(text);
1004 let trimmed = first_line.trim_end();
1006 let trimmed = trimmed.strip_suffix('{').unwrap_or(trimmed).trim_end();
1007 trimmed.to_string()
1008}
1009
1010fn extract_ts_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1012 let lang = LangId::TypeScript;
1013 let capture_names = query.capture_names();
1014
1015 let export_ranges = collect_export_ranges(source, root, query);
1016
1017 let mut symbols = Vec::new();
1018 let mut cursor = QueryCursor::new();
1019 let mut matches = cursor.matches(query, *root, source.as_bytes());
1020
1021 while let Some(m) = {
1022 matches.advance();
1023 matches.get()
1024 } {
1025 let mut fn_name_node = None;
1027 let mut fn_def_node = None;
1028 let mut arrow_name_node = None;
1029 let mut arrow_def_node = None;
1030 let mut class_name_node = None;
1031 let mut class_def_node = None;
1032 let mut method_class_name_node = None;
1033 let mut method_name_node = None;
1034 let mut method_def_node = None;
1035 let mut interface_name_node = None;
1036 let mut interface_def_node = None;
1037 let mut enum_name_node = None;
1038 let mut enum_def_node = None;
1039 let mut type_alias_name_node = None;
1040 let mut type_alias_def_node = None;
1041 let mut var_name_node = None;
1042 let mut var_def_node = None;
1043
1044 for cap in m.captures {
1045 let Some(&name) = capture_names.get(cap.index as usize) else {
1046 continue;
1047 };
1048 match name {
1049 "fn.name" => fn_name_node = Some(cap.node),
1050 "fn.def" => fn_def_node = Some(cap.node),
1051 "arrow.name" => arrow_name_node = Some(cap.node),
1052 "arrow.def" => arrow_def_node = Some(cap.node),
1053 "class.name" => class_name_node = Some(cap.node),
1054 "class.def" => class_def_node = Some(cap.node),
1055 "method.class_name" => method_class_name_node = Some(cap.node),
1056 "method.name" => method_name_node = Some(cap.node),
1057 "method.def" => method_def_node = Some(cap.node),
1058 "interface.name" => interface_name_node = Some(cap.node),
1059 "interface.def" => interface_def_node = Some(cap.node),
1060 "enum.name" => enum_name_node = Some(cap.node),
1061 "enum.def" => enum_def_node = Some(cap.node),
1062 "type_alias.name" => type_alias_name_node = Some(cap.node),
1063 "type_alias.def" => type_alias_def_node = Some(cap.node),
1064 "var.name" => var_name_node = Some(cap.node),
1065 "var.def" => var_def_node = Some(cap.node),
1066 _ => {}
1068 }
1069 }
1070
1071 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1073 symbols.push(Symbol {
1074 name: node_text(source, &name_node).to_string(),
1075 kind: SymbolKind::Function,
1076 range: node_range_with_decorators(&def_node, source, lang),
1077 signature: Some(extract_signature(source, &def_node)),
1078 scope_chain: vec![],
1079 exported: is_exported(&def_node, &export_ranges),
1080 parent: None,
1081 });
1082 }
1083
1084 if let (Some(name_node), Some(def_node)) = (arrow_name_node, arrow_def_node) {
1086 symbols.push(Symbol {
1087 name: node_text(source, &name_node).to_string(),
1088 kind: SymbolKind::Function,
1089 range: node_range_with_decorators(&def_node, source, lang),
1090 signature: Some(extract_signature(source, &def_node)),
1091 scope_chain: vec![],
1092 exported: is_exported(&def_node, &export_ranges),
1093 parent: None,
1094 });
1095 }
1096
1097 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
1099 symbols.push(Symbol {
1100 name: node_text(source, &name_node).to_string(),
1101 kind: SymbolKind::Class,
1102 range: node_range_with_decorators(&def_node, source, lang),
1103 signature: Some(extract_signature(source, &def_node)),
1104 scope_chain: vec![],
1105 exported: is_exported(&def_node, &export_ranges),
1106 parent: None,
1107 });
1108 }
1109
1110 if let (Some(class_name_node), Some(name_node), Some(def_node)) =
1112 (method_class_name_node, method_name_node, method_def_node)
1113 {
1114 let class_name = node_text(source, &class_name_node).to_string();
1115 symbols.push(Symbol {
1116 name: node_text(source, &name_node).to_string(),
1117 kind: SymbolKind::Method,
1118 range: node_range_with_decorators(&def_node, source, lang),
1119 signature: Some(extract_signature(source, &def_node)),
1120 scope_chain: vec![class_name.clone()],
1121 exported: false, parent: Some(class_name),
1123 });
1124 }
1125
1126 if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
1128 symbols.push(Symbol {
1129 name: node_text(source, &name_node).to_string(),
1130 kind: SymbolKind::Interface,
1131 range: node_range_with_decorators(&def_node, source, lang),
1132 signature: Some(extract_signature(source, &def_node)),
1133 scope_chain: vec![],
1134 exported: is_exported(&def_node, &export_ranges),
1135 parent: None,
1136 });
1137 }
1138
1139 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
1141 symbols.push(Symbol {
1142 name: node_text(source, &name_node).to_string(),
1143 kind: SymbolKind::Enum,
1144 range: node_range_with_decorators(&def_node, source, lang),
1145 signature: Some(extract_signature(source, &def_node)),
1146 scope_chain: vec![],
1147 exported: is_exported(&def_node, &export_ranges),
1148 parent: None,
1149 });
1150 }
1151
1152 if let (Some(name_node), Some(def_node)) = (type_alias_name_node, type_alias_def_node) {
1154 symbols.push(Symbol {
1155 name: node_text(source, &name_node).to_string(),
1156 kind: SymbolKind::TypeAlias,
1157 range: node_range_with_decorators(&def_node, source, lang),
1158 signature: Some(extract_signature(source, &def_node)),
1159 scope_chain: vec![],
1160 exported: is_exported(&def_node, &export_ranges),
1161 parent: None,
1162 });
1163 }
1164
1165 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
1167 let is_top_level = def_node
1169 .parent()
1170 .map(|p| p.kind() == "program" || p.kind() == "export_statement")
1171 .unwrap_or(false);
1172 let is_function_like = lexical_declaration_has_function_value(&def_node);
1173 let name = node_text(source, &name_node).to_string();
1174 let already_captured = symbols.iter().any(|s| s.name == name);
1175 if is_top_level && !is_function_like && !already_captured {
1176 symbols.push(Symbol {
1177 name,
1178 kind: SymbolKind::Variable,
1179 range: node_range_with_decorators(&def_node, source, lang),
1180 signature: Some(extract_signature(source, &def_node)),
1181 scope_chain: vec![],
1182 exported: is_exported(&def_node, &export_ranges),
1183 parent: None,
1184 });
1185 }
1186 }
1187 }
1188
1189 dedup_symbols(&mut symbols);
1191 Ok(symbols)
1192}
1193
1194fn extract_js_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1196 let lang = LangId::JavaScript;
1197 let capture_names = query.capture_names();
1198
1199 let export_ranges = collect_export_ranges(source, root, query);
1200
1201 let mut symbols = Vec::new();
1202 let mut cursor = QueryCursor::new();
1203 let mut matches = cursor.matches(query, *root, source.as_bytes());
1204
1205 while let Some(m) = {
1206 matches.advance();
1207 matches.get()
1208 } {
1209 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
1219 for cap in m.captures {
1220 let Some(&name) = capture_names.get(cap.index as usize) else {
1221 continue;
1222 };
1223 match name {
1224 "fn.name" => fn_name_node = Some(cap.node),
1225 "fn.def" => fn_def_node = Some(cap.node),
1226 "arrow.name" => arrow_name_node = Some(cap.node),
1227 "arrow.def" => arrow_def_node = Some(cap.node),
1228 "class.name" => class_name_node = Some(cap.node),
1229 "class.def" => class_def_node = Some(cap.node),
1230 "method.class_name" => method_class_name_node = Some(cap.node),
1231 "method.name" => method_name_node = Some(cap.node),
1232 "method.def" => method_def_node = Some(cap.node),
1233 _ => {}
1234 }
1235 }
1236
1237 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1238 symbols.push(Symbol {
1239 name: node_text(source, &name_node).to_string(),
1240 kind: SymbolKind::Function,
1241 range: node_range_with_decorators(&def_node, source, lang),
1242 signature: Some(extract_signature(source, &def_node)),
1243 scope_chain: vec![],
1244 exported: is_exported(&def_node, &export_ranges),
1245 parent: None,
1246 });
1247 }
1248
1249 if let (Some(name_node), Some(def_node)) = (arrow_name_node, arrow_def_node) {
1250 symbols.push(Symbol {
1251 name: node_text(source, &name_node).to_string(),
1252 kind: SymbolKind::Function,
1253 range: node_range_with_decorators(&def_node, source, lang),
1254 signature: Some(extract_signature(source, &def_node)),
1255 scope_chain: vec![],
1256 exported: is_exported(&def_node, &export_ranges),
1257 parent: None,
1258 });
1259 }
1260
1261 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
1262 symbols.push(Symbol {
1263 name: node_text(source, &name_node).to_string(),
1264 kind: SymbolKind::Class,
1265 range: node_range_with_decorators(&def_node, source, lang),
1266 signature: Some(extract_signature(source, &def_node)),
1267 scope_chain: vec![],
1268 exported: is_exported(&def_node, &export_ranges),
1269 parent: None,
1270 });
1271 }
1272
1273 if let (Some(class_name_node), Some(name_node), Some(def_node)) =
1274 (method_class_name_node, method_name_node, method_def_node)
1275 {
1276 let class_name = node_text(source, &class_name_node).to_string();
1277 symbols.push(Symbol {
1278 name: node_text(source, &name_node).to_string(),
1279 kind: SymbolKind::Method,
1280 range: node_range_with_decorators(&def_node, source, lang),
1281 signature: Some(extract_signature(source, &def_node)),
1282 scope_chain: vec![class_name.clone()],
1283 exported: false,
1284 parent: Some(class_name),
1285 });
1286 }
1287 }
1288
1289 dedup_symbols(&mut symbols);
1290 Ok(symbols)
1291}
1292
1293fn py_scope_chain(node: &Node, source: &str) -> Vec<String> {
1296 let mut chain = Vec::new();
1297 let mut current = node.parent();
1298 while let Some(parent) = current {
1299 if parent.kind() == "class_definition" {
1300 if let Some(name_node) = parent.child_by_field_name("name") {
1301 chain.push(node_text(source, &name_node).to_string());
1302 }
1303 }
1304 current = parent.parent();
1305 }
1306 chain.reverse();
1307 chain
1308}
1309
1310fn extract_py_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1312 let lang = LangId::Python;
1313 let capture_names = query.capture_names();
1314
1315 let mut symbols = Vec::new();
1316 let mut cursor = QueryCursor::new();
1317 let mut matches = cursor.matches(query, *root, source.as_bytes());
1318
1319 let mut decorated_fn_lines = std::collections::HashSet::new();
1321
1322 {
1324 let mut cursor2 = QueryCursor::new();
1325 let mut matches2 = cursor2.matches(query, *root, source.as_bytes());
1326 while let Some(m) = {
1327 matches2.advance();
1328 matches2.get()
1329 } {
1330 let mut dec_def_node = None;
1331 let mut dec_decorator_node = None;
1332
1333 for cap in m.captures {
1334 let Some(&name) = capture_names.get(cap.index as usize) else {
1335 continue;
1336 };
1337 match name {
1338 "dec.def" => dec_def_node = Some(cap.node),
1339 "dec.decorator" => dec_decorator_node = Some(cap.node),
1340 _ => {}
1341 }
1342 }
1343
1344 if let (Some(def_node), Some(_dec_node)) = (dec_def_node, dec_decorator_node) {
1345 let mut child_cursor = def_node.walk();
1347 if child_cursor.goto_first_child() {
1348 loop {
1349 let child = child_cursor.node();
1350 if child.kind() == "function_definition"
1351 || child.kind() == "class_definition"
1352 {
1353 decorated_fn_lines.insert(child.start_position().row);
1354 }
1355 if !child_cursor.goto_next_sibling() {
1356 break;
1357 }
1358 }
1359 }
1360 }
1361 }
1362 }
1363
1364 while let Some(m) = {
1365 matches.advance();
1366 matches.get()
1367 } {
1368 let mut fn_name_node = None;
1369 let mut fn_def_node = None;
1370 let mut class_name_node = None;
1371 let mut class_def_node = None;
1372
1373 for cap in m.captures {
1374 let Some(&name) = capture_names.get(cap.index as usize) else {
1375 continue;
1376 };
1377 match name {
1378 "fn.name" => fn_name_node = Some(cap.node),
1379 "fn.def" => fn_def_node = Some(cap.node),
1380 "class.name" => class_name_node = Some(cap.node),
1381 "class.def" => class_def_node = Some(cap.node),
1382 _ => {}
1383 }
1384 }
1385
1386 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1388 let scope = py_scope_chain(&def_node, source);
1389 let is_method = !scope.is_empty();
1390 let name = node_text(source, &name_node).to_string();
1391 let kind = if is_method {
1393 SymbolKind::Method
1394 } else {
1395 SymbolKind::Function
1396 };
1397
1398 let sig = if decorated_fn_lines.contains(&def_node.start_position().row) {
1400 let mut sig_parts = Vec::new();
1402 let mut parent = def_node.parent();
1403 while let Some(p) = parent {
1404 if p.kind() == "decorated_definition" {
1405 let mut dc = p.walk();
1407 if dc.goto_first_child() {
1408 loop {
1409 if dc.node().kind() == "decorator" {
1410 sig_parts.push(node_text(source, &dc.node()).to_string());
1411 }
1412 if !dc.goto_next_sibling() {
1413 break;
1414 }
1415 }
1416 }
1417 break;
1418 }
1419 parent = p.parent();
1420 }
1421 sig_parts.push(extract_signature(source, &def_node));
1422 Some(sig_parts.join("\n"))
1423 } else {
1424 Some(extract_signature(source, &def_node))
1425 };
1426
1427 symbols.push(Symbol {
1428 name,
1429 kind,
1430 range: node_range_with_decorators(&def_node, source, lang),
1431 signature: sig,
1432 scope_chain: scope.clone(),
1433 exported: false, parent: scope.last().cloned(),
1435 });
1436 }
1437
1438 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
1440 let scope = py_scope_chain(&def_node, source);
1441
1442 let sig = if decorated_fn_lines.contains(&def_node.start_position().row) {
1444 let mut sig_parts = Vec::new();
1445 let mut parent = def_node.parent();
1446 while let Some(p) = parent {
1447 if p.kind() == "decorated_definition" {
1448 let mut dc = p.walk();
1449 if dc.goto_first_child() {
1450 loop {
1451 if dc.node().kind() == "decorator" {
1452 sig_parts.push(node_text(source, &dc.node()).to_string());
1453 }
1454 if !dc.goto_next_sibling() {
1455 break;
1456 }
1457 }
1458 }
1459 break;
1460 }
1461 parent = p.parent();
1462 }
1463 sig_parts.push(extract_signature(source, &def_node));
1464 Some(sig_parts.join("\n"))
1465 } else {
1466 Some(extract_signature(source, &def_node))
1467 };
1468
1469 symbols.push(Symbol {
1470 name: node_text(source, &name_node).to_string(),
1471 kind: SymbolKind::Class,
1472 range: node_range_with_decorators(&def_node, source, lang),
1473 signature: sig,
1474 scope_chain: scope.clone(),
1475 exported: false,
1476 parent: scope.last().cloned(),
1477 });
1478 }
1479 }
1480
1481 dedup_symbols(&mut symbols);
1482 Ok(symbols)
1483}
1484
1485fn extract_rs_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1488 let lang = LangId::Rust;
1489 let capture_names = query.capture_names();
1490
1491 let mut vis_ranges: Vec<std::ops::Range<usize>> = Vec::new();
1493 {
1494 let vis_idx = capture_names.iter().position(|n| *n == "vis.mod");
1495 if let Some(idx) = vis_idx {
1496 let idx = idx as u32;
1497 let mut cursor = QueryCursor::new();
1498 let mut matches = cursor.matches(query, *root, source.as_bytes());
1499 while let Some(m) = {
1500 matches.advance();
1501 matches.get()
1502 } {
1503 for cap in m.captures {
1504 if cap.index == idx {
1505 vis_ranges.push(cap.node.byte_range());
1506 }
1507 }
1508 }
1509 }
1510 }
1511
1512 let is_pub = |node: &Node| -> bool {
1513 let mut child_cursor = node.walk();
1515 if child_cursor.goto_first_child() {
1516 loop {
1517 if child_cursor.node().kind() == "visibility_modifier" {
1518 return true;
1519 }
1520 if !child_cursor.goto_next_sibling() {
1521 break;
1522 }
1523 }
1524 }
1525 false
1526 };
1527
1528 let mut symbols = Vec::new();
1529 let mut cursor = QueryCursor::new();
1530 let mut matches = cursor.matches(query, *root, source.as_bytes());
1531
1532 while let Some(m) = {
1533 matches.advance();
1534 matches.get()
1535 } {
1536 let mut fn_name_node = None;
1537 let mut fn_def_node = None;
1538 let mut struct_name_node = None;
1539 let mut struct_def_node = None;
1540 let mut enum_name_node = None;
1541 let mut enum_def_node = None;
1542 let mut trait_name_node = None;
1543 let mut trait_def_node = None;
1544 let mut impl_def_node = None;
1545
1546 for cap in m.captures {
1547 let Some(&name) = capture_names.get(cap.index as usize) else {
1548 continue;
1549 };
1550 match name {
1551 "fn.name" => fn_name_node = Some(cap.node),
1552 "fn.def" => fn_def_node = Some(cap.node),
1553 "struct.name" => struct_name_node = Some(cap.node),
1554 "struct.def" => struct_def_node = Some(cap.node),
1555 "enum.name" => enum_name_node = Some(cap.node),
1556 "enum.def" => enum_def_node = Some(cap.node),
1557 "trait.name" => trait_name_node = Some(cap.node),
1558 "trait.def" => trait_def_node = Some(cap.node),
1559 "impl.def" => impl_def_node = Some(cap.node),
1560 _ => {}
1561 }
1562 }
1563
1564 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1566 let parent = def_node.parent();
1567 let in_impl = parent
1568 .map(|p| p.kind() == "declaration_list")
1569 .unwrap_or(false);
1570 if !in_impl {
1571 symbols.push(Symbol {
1572 name: node_text(source, &name_node).to_string(),
1573 kind: SymbolKind::Function,
1574 range: node_range_with_decorators(&def_node, source, lang),
1575 signature: Some(extract_signature(source, &def_node)),
1576 scope_chain: vec![],
1577 exported: is_pub(&def_node),
1578 parent: None,
1579 });
1580 }
1581 }
1582
1583 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
1585 symbols.push(Symbol {
1586 name: node_text(source, &name_node).to_string(),
1587 kind: SymbolKind::Struct,
1588 range: node_range_with_decorators(&def_node, source, lang),
1589 signature: Some(extract_signature(source, &def_node)),
1590 scope_chain: vec![],
1591 exported: is_pub(&def_node),
1592 parent: None,
1593 });
1594 }
1595
1596 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
1598 symbols.push(Symbol {
1599 name: node_text(source, &name_node).to_string(),
1600 kind: SymbolKind::Enum,
1601 range: node_range_with_decorators(&def_node, source, lang),
1602 signature: Some(extract_signature(source, &def_node)),
1603 scope_chain: vec![],
1604 exported: is_pub(&def_node),
1605 parent: None,
1606 });
1607 }
1608
1609 if let (Some(name_node), Some(def_node)) = (trait_name_node, trait_def_node) {
1611 symbols.push(Symbol {
1612 name: node_text(source, &name_node).to_string(),
1613 kind: SymbolKind::Interface,
1614 range: node_range_with_decorators(&def_node, source, lang),
1615 signature: Some(extract_signature(source, &def_node)),
1616 scope_chain: vec![],
1617 exported: is_pub(&def_node),
1618 parent: None,
1619 });
1620 }
1621
1622 if let Some(impl_node) = impl_def_node {
1624 let mut type_names: Vec<String> = Vec::new();
1628 let mut child_cursor = impl_node.walk();
1629 if child_cursor.goto_first_child() {
1630 loop {
1631 let child = child_cursor.node();
1632 if child.kind() == "type_identifier" || child.kind() == "generic_type" {
1633 type_names.push(node_text(source, &child).to_string());
1634 }
1635 if !child_cursor.goto_next_sibling() {
1636 break;
1637 }
1638 }
1639 }
1640
1641 let scope_name = if type_names.len() >= 2 {
1642 format!("{} for {}", type_names[0], type_names[1])
1644 } else if type_names.len() == 1 {
1645 type_names[0].clone()
1646 } else {
1647 String::new()
1648 };
1649
1650 let parent_name = type_names.last().cloned().unwrap_or_default();
1651
1652 let mut child_cursor = impl_node.walk();
1654 if child_cursor.goto_first_child() {
1655 loop {
1656 let child = child_cursor.node();
1657 if child.kind() == "declaration_list" {
1658 let mut fn_cursor = child.walk();
1659 if fn_cursor.goto_first_child() {
1660 loop {
1661 let fn_node = fn_cursor.node();
1662 if fn_node.kind() == "function_item" {
1663 if let Some(name_node) = fn_node.child_by_field_name("name") {
1664 symbols.push(Symbol {
1665 name: node_text(source, &name_node).to_string(),
1666 kind: SymbolKind::Method,
1667 range: node_range_with_decorators(
1668 &fn_node, source, lang,
1669 ),
1670 signature: Some(extract_signature(source, &fn_node)),
1671 scope_chain: if scope_name.is_empty() {
1672 vec![]
1673 } else {
1674 vec![scope_name.clone()]
1675 },
1676 exported: is_pub(&fn_node),
1677 parent: if parent_name.is_empty() {
1678 None
1679 } else {
1680 Some(parent_name.clone())
1681 },
1682 });
1683 }
1684 }
1685 if !fn_cursor.goto_next_sibling() {
1686 break;
1687 }
1688 }
1689 }
1690 }
1691 if !child_cursor.goto_next_sibling() {
1692 break;
1693 }
1694 }
1695 }
1696 }
1697 }
1698
1699 dedup_symbols(&mut symbols);
1700 Ok(symbols)
1701}
1702
1703fn extract_go_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1707 let lang = LangId::Go;
1708 let capture_names = query.capture_names();
1709
1710 let is_go_exported = |name: &str| -> bool {
1711 name.chars()
1712 .next()
1713 .map(|c| c.is_uppercase())
1714 .unwrap_or(false)
1715 };
1716
1717 let mut symbols = Vec::new();
1718 let mut cursor = QueryCursor::new();
1719 let mut matches = cursor.matches(query, *root, source.as_bytes());
1720
1721 while let Some(m) = {
1722 matches.advance();
1723 matches.get()
1724 } {
1725 let mut fn_name_node = None;
1726 let mut fn_def_node = None;
1727 let mut method_name_node = None;
1728 let mut method_def_node = None;
1729 let mut type_name_node = None;
1730 let mut type_body_node = None;
1731 let mut type_def_node = None;
1732
1733 for cap in m.captures {
1734 let Some(&name) = capture_names.get(cap.index as usize) else {
1735 continue;
1736 };
1737 match name {
1738 "fn.name" => fn_name_node = Some(cap.node),
1739 "fn.def" => fn_def_node = Some(cap.node),
1740 "method.name" => method_name_node = Some(cap.node),
1741 "method.def" => method_def_node = Some(cap.node),
1742 "type.name" => type_name_node = Some(cap.node),
1743 "type.body" => type_body_node = Some(cap.node),
1744 "type.def" => type_def_node = Some(cap.node),
1745 _ => {}
1746 }
1747 }
1748
1749 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1751 let name = node_text(source, &name_node).to_string();
1752 symbols.push(Symbol {
1753 exported: is_go_exported(&name),
1754 name,
1755 kind: SymbolKind::Function,
1756 range: node_range_with_decorators(&def_node, source, lang),
1757 signature: Some(extract_signature(source, &def_node)),
1758 scope_chain: vec![],
1759 parent: None,
1760 });
1761 }
1762
1763 if let (Some(name_node), Some(def_node)) = (method_name_node, method_def_node) {
1765 let name = node_text(source, &name_node).to_string();
1766
1767 let receiver_type = extract_go_receiver_type(&def_node, source);
1769 let scope_chain = if let Some(ref rt) = receiver_type {
1770 vec![rt.clone()]
1771 } else {
1772 vec![]
1773 };
1774
1775 symbols.push(Symbol {
1776 exported: is_go_exported(&name),
1777 name,
1778 kind: SymbolKind::Method,
1779 range: node_range_with_decorators(&def_node, source, lang),
1780 signature: Some(extract_signature(source, &def_node)),
1781 scope_chain,
1782 parent: receiver_type,
1783 });
1784 }
1785
1786 if let (Some(name_node), Some(body_node), Some(def_node)) =
1788 (type_name_node, type_body_node, type_def_node)
1789 {
1790 let name = node_text(source, &name_node).to_string();
1791 let kind = match body_node.kind() {
1792 "struct_type" => SymbolKind::Struct,
1793 "interface_type" => SymbolKind::Interface,
1794 _ => SymbolKind::TypeAlias,
1795 };
1796
1797 symbols.push(Symbol {
1798 exported: is_go_exported(&name),
1799 name,
1800 kind,
1801 range: node_range_with_decorators(&def_node, source, lang),
1802 signature: Some(extract_signature(source, &def_node)),
1803 scope_chain: vec![],
1804 parent: None,
1805 });
1806 }
1807 }
1808
1809 dedup_symbols(&mut symbols);
1810 Ok(symbols)
1811}
1812
1813fn extract_go_receiver_type(method_node: &Node, source: &str) -> Option<String> {
1816 let mut child_cursor = method_node.walk();
1818 if child_cursor.goto_first_child() {
1819 loop {
1820 let child = child_cursor.node();
1821 if child.kind() == "parameter_list" {
1822 return find_type_identifier_recursive(&child, source);
1824 }
1825 if !child_cursor.goto_next_sibling() {
1826 break;
1827 }
1828 }
1829 }
1830 None
1831}
1832
1833fn split_scope_text(text: &str, separator: &str) -> Vec<String> {
1834 text.split(separator)
1835 .map(str::trim)
1836 .filter(|segment| !segment.is_empty())
1837 .map(ToString::to_string)
1838 .collect()
1839}
1840
1841fn last_scope_segment(text: &str, separator: &str) -> String {
1842 split_scope_text(text, separator)
1843 .pop()
1844 .unwrap_or_else(|| text.trim().to_string())
1845}
1846
1847fn zig_container_scope_chain(node: &Node, source: &str) -> Vec<String> {
1848 let mut chain = Vec::new();
1849 let mut current = node.parent();
1850
1851 while let Some(parent) = current {
1852 if matches!(
1853 parent.kind(),
1854 "struct_declaration" | "enum_declaration" | "union_declaration" | "opaque_declaration"
1855 ) {
1856 if let Some(container) = parent.parent() {
1857 if container.kind() == "variable_declaration" {
1858 let mut cursor = container.walk();
1859 if cursor.goto_first_child() {
1860 loop {
1861 let child = cursor.node();
1862 if child.kind() == "identifier" {
1863 chain.push(node_text(source, &child).to_string());
1864 break;
1865 }
1866 if !cursor.goto_next_sibling() {
1867 break;
1868 }
1869 }
1870 }
1871 }
1872 }
1873 }
1874 current = parent.parent();
1875 }
1876
1877 chain.reverse();
1878 chain
1879}
1880
1881fn csharp_scope_chain(node: &Node, source: &str) -> Vec<String> {
1882 let mut chain = Vec::new();
1883 let mut current = node.parent();
1884
1885 while let Some(parent) = current {
1886 match parent.kind() {
1887 "namespace_declaration" | "file_scoped_namespace_declaration" => {
1888 if let Some(name_node) = parent.child_by_field_name("name") {
1889 chain.push(node_text(source, &name_node).to_string());
1890 }
1891 }
1892 "class_declaration"
1893 | "interface_declaration"
1894 | "struct_declaration"
1895 | "record_declaration" => {
1896 if let Some(name_node) = parent.child_by_field_name("name") {
1897 chain.push(node_text(source, &name_node).to_string());
1898 }
1899 }
1900 _ => {}
1901 }
1902 current = parent.parent();
1903 }
1904
1905 chain.reverse();
1906 chain
1907}
1908
1909fn cpp_parent_scope_chain(node: &Node, source: &str) -> Vec<String> {
1910 let mut chain = Vec::new();
1911 let mut current = node.parent();
1912
1913 while let Some(parent) = current {
1914 match parent.kind() {
1915 "namespace_definition" => {
1916 if let Some(name_node) = parent.child_by_field_name("name") {
1917 chain.push(node_text(source, &name_node).to_string());
1918 }
1919 }
1920 "class_specifier" | "struct_specifier" => {
1921 if let Some(name_node) = parent.child_by_field_name("name") {
1922 chain.push(last_scope_segment(node_text(source, &name_node), "::"));
1923 }
1924 }
1925 _ => {}
1926 }
1927 current = parent.parent();
1928 }
1929
1930 chain.reverse();
1931 chain
1932}
1933
1934fn template_signature(source: &str, template_node: &Node, item_node: &Node) -> String {
1935 format!(
1936 "{}\n{}",
1937 extract_signature(source, template_node),
1938 extract_signature(source, item_node)
1939 )
1940}
1941
1942fn extract_c_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1944 let lang = LangId::C;
1945 let capture_names = query.capture_names();
1946
1947 let mut symbols = Vec::new();
1948 let mut cursor = QueryCursor::new();
1949 let mut matches = cursor.matches(query, *root, source.as_bytes());
1950
1951 while let Some(m) = {
1952 matches.advance();
1953 matches.get()
1954 } {
1955 let mut fn_name_node = None;
1956 let mut fn_def_node = None;
1957 let mut struct_name_node = None;
1958 let mut struct_def_node = None;
1959 let mut enum_name_node = None;
1960 let mut enum_def_node = None;
1961 let mut type_name_node = None;
1962 let mut type_def_node = None;
1963 let mut macro_name_node = None;
1964 let mut macro_def_node = None;
1965
1966 for cap in m.captures {
1967 let Some(&name) = capture_names.get(cap.index as usize) else {
1968 continue;
1969 };
1970 match name {
1971 "fn.name" => fn_name_node = Some(cap.node),
1972 "fn.def" => fn_def_node = Some(cap.node),
1973 "struct.name" => struct_name_node = Some(cap.node),
1974 "struct.def" => struct_def_node = Some(cap.node),
1975 "enum.name" => enum_name_node = Some(cap.node),
1976 "enum.def" => enum_def_node = Some(cap.node),
1977 "type.name" => type_name_node = Some(cap.node),
1978 "type.def" => type_def_node = Some(cap.node),
1979 "macro.name" => macro_name_node = Some(cap.node),
1980 "macro.def" => macro_def_node = Some(cap.node),
1981 _ => {}
1982 }
1983 }
1984
1985 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1986 symbols.push(Symbol {
1987 name: node_text(source, &name_node).to_string(),
1988 kind: SymbolKind::Function,
1989 range: node_range_with_decorators(&def_node, source, lang),
1990 signature: Some(extract_signature(source, &def_node)),
1991 scope_chain: vec![],
1992 exported: false,
1993 parent: None,
1994 });
1995 }
1996
1997 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
1998 symbols.push(Symbol {
1999 name: node_text(source, &name_node).to_string(),
2000 kind: SymbolKind::Struct,
2001 range: node_range_with_decorators(&def_node, source, lang),
2002 signature: Some(extract_signature(source, &def_node)),
2003 scope_chain: vec![],
2004 exported: false,
2005 parent: None,
2006 });
2007 }
2008
2009 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
2010 symbols.push(Symbol {
2011 name: node_text(source, &name_node).to_string(),
2012 kind: SymbolKind::Enum,
2013 range: node_range_with_decorators(&def_node, source, lang),
2014 signature: Some(extract_signature(source, &def_node)),
2015 scope_chain: vec![],
2016 exported: false,
2017 parent: None,
2018 });
2019 }
2020
2021 if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
2022 symbols.push(Symbol {
2023 name: node_text(source, &name_node).to_string(),
2024 kind: SymbolKind::TypeAlias,
2025 range: node_range_with_decorators(&def_node, source, lang),
2026 signature: Some(extract_signature(source, &def_node)),
2027 scope_chain: vec![],
2028 exported: false,
2029 parent: None,
2030 });
2031 }
2032
2033 if let (Some(name_node), Some(def_node)) = (macro_name_node, macro_def_node) {
2034 symbols.push(Symbol {
2035 name: node_text(source, &name_node).to_string(),
2036 kind: SymbolKind::Variable,
2037 range: node_range(&def_node),
2038 signature: Some(extract_signature(source, &def_node)),
2039 scope_chain: vec![],
2040 exported: false,
2041 parent: None,
2042 });
2043 }
2044 }
2045
2046 dedup_symbols(&mut symbols);
2047 Ok(symbols)
2048}
2049
2050fn extract_cpp_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2052 let lang = LangId::Cpp;
2053 let capture_names = query.capture_names();
2054
2055 let mut type_names = HashSet::new();
2056 {
2057 let mut cursor = QueryCursor::new();
2058 let mut matches = cursor.matches(query, *root, source.as_bytes());
2059 while let Some(m) = {
2060 matches.advance();
2061 matches.get()
2062 } {
2063 for cap in m.captures {
2064 let Some(&name) = capture_names.get(cap.index as usize) else {
2065 continue;
2066 };
2067 match name {
2068 "class.name"
2069 | "struct.name"
2070 | "template.class.name"
2071 | "template.struct.name" => {
2072 type_names.insert(last_scope_segment(node_text(source, &cap.node), "::"));
2073 }
2074 _ => {}
2075 }
2076 }
2077 }
2078 }
2079
2080 let mut symbols = Vec::new();
2081 let mut cursor = QueryCursor::new();
2082 let mut matches = cursor.matches(query, *root, source.as_bytes());
2083
2084 while let Some(m) = {
2085 matches.advance();
2086 matches.get()
2087 } {
2088 let mut fn_name_node = None;
2089 let mut fn_def_node = None;
2090 let mut method_name_node = None;
2091 let mut method_def_node = None;
2092 let mut qual_scope_node = None;
2093 let mut qual_name_node = None;
2094 let mut qual_def_node = None;
2095 let mut class_name_node = None;
2096 let mut class_def_node = None;
2097 let mut struct_name_node = None;
2098 let mut struct_def_node = None;
2099 let mut enum_name_node = None;
2100 let mut enum_def_node = None;
2101 let mut namespace_name_node = None;
2102 let mut namespace_def_node = None;
2103 let mut template_class_name_node = None;
2104 let mut template_class_def_node = None;
2105 let mut template_class_item_node = None;
2106 let mut template_struct_name_node = None;
2107 let mut template_struct_def_node = None;
2108 let mut template_struct_item_node = None;
2109 let mut template_fn_name_node = None;
2110 let mut template_fn_def_node = None;
2111 let mut template_fn_item_node = None;
2112 let mut template_qual_scope_node = None;
2113 let mut template_qual_name_node = None;
2114 let mut template_qual_def_node = None;
2115 let mut template_qual_item_node = None;
2116
2117 for cap in m.captures {
2118 let Some(&name) = capture_names.get(cap.index as usize) else {
2119 continue;
2120 };
2121 match name {
2122 "fn.name" => fn_name_node = Some(cap.node),
2123 "fn.def" => fn_def_node = Some(cap.node),
2124 "method.name" => method_name_node = Some(cap.node),
2125 "method.def" => method_def_node = Some(cap.node),
2126 "qual.scope" => qual_scope_node = Some(cap.node),
2127 "qual.name" => qual_name_node = Some(cap.node),
2128 "qual.def" => qual_def_node = Some(cap.node),
2129 "class.name" => class_name_node = Some(cap.node),
2130 "class.def" => class_def_node = Some(cap.node),
2131 "struct.name" => struct_name_node = Some(cap.node),
2132 "struct.def" => struct_def_node = Some(cap.node),
2133 "enum.name" => enum_name_node = Some(cap.node),
2134 "enum.def" => enum_def_node = Some(cap.node),
2135 "namespace.name" => namespace_name_node = Some(cap.node),
2136 "namespace.def" => namespace_def_node = Some(cap.node),
2137 "template.class.name" => template_class_name_node = Some(cap.node),
2138 "template.class.def" => template_class_def_node = Some(cap.node),
2139 "template.class.item" => template_class_item_node = Some(cap.node),
2140 "template.struct.name" => template_struct_name_node = Some(cap.node),
2141 "template.struct.def" => template_struct_def_node = Some(cap.node),
2142 "template.struct.item" => template_struct_item_node = Some(cap.node),
2143 "template.fn.name" => template_fn_name_node = Some(cap.node),
2144 "template.fn.def" => template_fn_def_node = Some(cap.node),
2145 "template.fn.item" => template_fn_item_node = Some(cap.node),
2146 "template.qual.scope" => template_qual_scope_node = Some(cap.node),
2147 "template.qual.name" => template_qual_name_node = Some(cap.node),
2148 "template.qual.def" => template_qual_def_node = Some(cap.node),
2149 "template.qual.item" => template_qual_item_node = Some(cap.node),
2150 _ => {}
2151 }
2152 }
2153
2154 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2155 let in_template = def_node
2156 .parent()
2157 .map(|parent| parent.kind() == "template_declaration")
2158 .unwrap_or(false);
2159 if !in_template {
2160 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2161 symbols.push(Symbol {
2162 name: node_text(source, &name_node).to_string(),
2163 kind: SymbolKind::Function,
2164 range: node_range_with_decorators(&def_node, source, lang),
2165 signature: Some(extract_signature(source, &def_node)),
2166 scope_chain: scope_chain.clone(),
2167 exported: false,
2168 parent: scope_chain.last().cloned(),
2169 });
2170 }
2171 }
2172
2173 if let (Some(name_node), Some(def_node)) = (method_name_node, method_def_node) {
2174 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2175 symbols.push(Symbol {
2176 name: node_text(source, &name_node).to_string(),
2177 kind: SymbolKind::Method,
2178 range: node_range_with_decorators(&def_node, source, lang),
2179 signature: Some(extract_signature(source, &def_node)),
2180 scope_chain: scope_chain.clone(),
2181 exported: false,
2182 parent: scope_chain.last().cloned(),
2183 });
2184 }
2185
2186 if let (Some(scope_node), Some(name_node), Some(def_node)) =
2187 (qual_scope_node, qual_name_node, qual_def_node)
2188 {
2189 let in_template = def_node
2190 .parent()
2191 .map(|parent| parent.kind() == "template_declaration")
2192 .unwrap_or(false);
2193 if !in_template {
2194 let scope_text = node_text(source, &scope_node);
2195 let scope_chain = split_scope_text(scope_text, "::");
2196 let parent = scope_chain.last().cloned();
2197 let kind = if parent
2198 .as_ref()
2199 .map(|segment| type_names.contains(segment))
2200 .unwrap_or(false)
2201 {
2202 SymbolKind::Method
2203 } else {
2204 SymbolKind::Function
2205 };
2206
2207 symbols.push(Symbol {
2208 name: node_text(source, &name_node).to_string(),
2209 kind,
2210 range: node_range_with_decorators(&def_node, source, lang),
2211 signature: Some(extract_signature(source, &def_node)),
2212 scope_chain,
2213 exported: false,
2214 parent,
2215 });
2216 }
2217 }
2218
2219 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
2220 let in_template = def_node
2221 .parent()
2222 .map(|parent| parent.kind() == "template_declaration")
2223 .unwrap_or(false);
2224 if !in_template {
2225 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2226 let name = last_scope_segment(node_text(source, &name_node), "::");
2227 symbols.push(Symbol {
2228 name: name.clone(),
2229 kind: SymbolKind::Class,
2230 range: node_range_with_decorators(&def_node, source, lang),
2231 signature: Some(extract_signature(source, &def_node)),
2232 scope_chain: scope_chain.clone(),
2233 exported: false,
2234 parent: scope_chain.last().cloned(),
2235 });
2236 }
2237 }
2238
2239 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
2240 let in_template = def_node
2241 .parent()
2242 .map(|parent| parent.kind() == "template_declaration")
2243 .unwrap_or(false);
2244 if !in_template {
2245 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2246 let name = last_scope_segment(node_text(source, &name_node), "::");
2247 symbols.push(Symbol {
2248 name: name.clone(),
2249 kind: SymbolKind::Struct,
2250 range: node_range_with_decorators(&def_node, source, lang),
2251 signature: Some(extract_signature(source, &def_node)),
2252 scope_chain: scope_chain.clone(),
2253 exported: false,
2254 parent: scope_chain.last().cloned(),
2255 });
2256 }
2257 }
2258
2259 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
2260 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2261 let name = last_scope_segment(node_text(source, &name_node), "::");
2262 symbols.push(Symbol {
2263 name: name.clone(),
2264 kind: SymbolKind::Enum,
2265 range: node_range_with_decorators(&def_node, source, lang),
2266 signature: Some(extract_signature(source, &def_node)),
2267 scope_chain: scope_chain.clone(),
2268 exported: false,
2269 parent: scope_chain.last().cloned(),
2270 });
2271 }
2272
2273 if let (Some(name_node), Some(def_node)) = (namespace_name_node, namespace_def_node) {
2274 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2275 symbols.push(Symbol {
2276 name: node_text(source, &name_node).to_string(),
2277 kind: SymbolKind::TypeAlias,
2278 range: node_range_with_decorators(&def_node, source, lang),
2279 signature: Some(extract_signature(source, &def_node)),
2280 scope_chain: scope_chain.clone(),
2281 exported: false,
2282 parent: scope_chain.last().cloned(),
2283 });
2284 }
2285
2286 if let (Some(name_node), Some(def_node), Some(item_node)) = (
2287 template_class_name_node,
2288 template_class_def_node,
2289 template_class_item_node,
2290 ) {
2291 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2292 let name = last_scope_segment(node_text(source, &name_node), "::");
2293 symbols.push(Symbol {
2294 name: name.clone(),
2295 kind: SymbolKind::Class,
2296 range: node_range_with_decorators(&def_node, source, lang),
2297 signature: Some(template_signature(source, &def_node, &item_node)),
2298 scope_chain: scope_chain.clone(),
2299 exported: false,
2300 parent: scope_chain.last().cloned(),
2301 });
2302 }
2303
2304 if let (Some(name_node), Some(def_node), Some(item_node)) = (
2305 template_struct_name_node,
2306 template_struct_def_node,
2307 template_struct_item_node,
2308 ) {
2309 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2310 let name = last_scope_segment(node_text(source, &name_node), "::");
2311 symbols.push(Symbol {
2312 name: name.clone(),
2313 kind: SymbolKind::Struct,
2314 range: node_range_with_decorators(&def_node, source, lang),
2315 signature: Some(template_signature(source, &def_node, &item_node)),
2316 scope_chain: scope_chain.clone(),
2317 exported: false,
2318 parent: scope_chain.last().cloned(),
2319 });
2320 }
2321
2322 if let (Some(name_node), Some(def_node), Some(item_node)) = (
2323 template_fn_name_node,
2324 template_fn_def_node,
2325 template_fn_item_node,
2326 ) {
2327 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2328 symbols.push(Symbol {
2329 name: node_text(source, &name_node).to_string(),
2330 kind: SymbolKind::Function,
2331 range: node_range_with_decorators(&def_node, source, lang),
2332 signature: Some(template_signature(source, &def_node, &item_node)),
2333 scope_chain: scope_chain.clone(),
2334 exported: false,
2335 parent: scope_chain.last().cloned(),
2336 });
2337 }
2338
2339 if let (Some(scope_node), Some(name_node), Some(def_node), Some(item_node)) = (
2340 template_qual_scope_node,
2341 template_qual_name_node,
2342 template_qual_def_node,
2343 template_qual_item_node,
2344 ) {
2345 let scope_chain = split_scope_text(node_text(source, &scope_node), "::");
2346 let parent = scope_chain.last().cloned();
2347 let kind = if parent
2348 .as_ref()
2349 .map(|segment| type_names.contains(segment))
2350 .unwrap_or(false)
2351 {
2352 SymbolKind::Method
2353 } else {
2354 SymbolKind::Function
2355 };
2356
2357 symbols.push(Symbol {
2358 name: node_text(source, &name_node).to_string(),
2359 kind,
2360 range: node_range_with_decorators(&def_node, source, lang),
2361 signature: Some(template_signature(source, &def_node, &item_node)),
2362 scope_chain,
2363 exported: false,
2364 parent,
2365 });
2366 }
2367 }
2368
2369 dedup_symbols(&mut symbols);
2370 Ok(symbols)
2371}
2372
2373fn extract_zig_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2375 let lang = LangId::Zig;
2376 let capture_names = query.capture_names();
2377
2378 let mut symbols = Vec::new();
2379 let mut cursor = QueryCursor::new();
2380 let mut matches = cursor.matches(query, *root, source.as_bytes());
2381
2382 while let Some(m) = {
2383 matches.advance();
2384 matches.get()
2385 } {
2386 let mut fn_name_node = None;
2387 let mut fn_def_node = None;
2388 let mut struct_name_node = None;
2389 let mut struct_def_node = None;
2390 let mut enum_name_node = None;
2391 let mut enum_def_node = None;
2392 let mut union_name_node = None;
2393 let mut union_def_node = None;
2394 let mut const_name_node = None;
2395 let mut const_def_node = None;
2396 let mut test_name_node = None;
2397 let mut test_def_node = None;
2398
2399 for cap in m.captures {
2400 let Some(&name) = capture_names.get(cap.index as usize) else {
2401 continue;
2402 };
2403 match name {
2404 "fn.name" => fn_name_node = Some(cap.node),
2405 "fn.def" => fn_def_node = Some(cap.node),
2406 "struct.name" => struct_name_node = Some(cap.node),
2407 "struct.def" => struct_def_node = Some(cap.node),
2408 "enum.name" => enum_name_node = Some(cap.node),
2409 "enum.def" => enum_def_node = Some(cap.node),
2410 "union.name" => union_name_node = Some(cap.node),
2411 "union.def" => union_def_node = Some(cap.node),
2412 "const.name" => const_name_node = Some(cap.node),
2413 "const.def" => const_def_node = Some(cap.node),
2414 "test.name" => test_name_node = Some(cap.node),
2415 "test.def" => test_def_node = Some(cap.node),
2416 _ => {}
2417 }
2418 }
2419
2420 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2421 let scope_chain = zig_container_scope_chain(&def_node, source);
2422 let kind = if scope_chain.is_empty() {
2423 SymbolKind::Function
2424 } else {
2425 SymbolKind::Method
2426 };
2427 symbols.push(Symbol {
2428 name: node_text(source, &name_node).to_string(),
2429 kind,
2430 range: node_range_with_decorators(&def_node, source, lang),
2431 signature: Some(extract_signature(source, &def_node)),
2432 scope_chain: scope_chain.clone(),
2433 exported: false,
2434 parent: scope_chain.last().cloned(),
2435 });
2436 }
2437
2438 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
2439 symbols.push(Symbol {
2440 name: node_text(source, &name_node).to_string(),
2441 kind: SymbolKind::Struct,
2442 range: node_range_with_decorators(&def_node, source, lang),
2443 signature: Some(extract_signature(source, &def_node)),
2444 scope_chain: vec![],
2445 exported: false,
2446 parent: None,
2447 });
2448 }
2449
2450 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
2451 symbols.push(Symbol {
2452 name: node_text(source, &name_node).to_string(),
2453 kind: SymbolKind::Enum,
2454 range: node_range_with_decorators(&def_node, source, lang),
2455 signature: Some(extract_signature(source, &def_node)),
2456 scope_chain: vec![],
2457 exported: false,
2458 parent: None,
2459 });
2460 }
2461
2462 if let (Some(name_node), Some(def_node)) = (union_name_node, union_def_node) {
2463 symbols.push(Symbol {
2464 name: node_text(source, &name_node).to_string(),
2465 kind: SymbolKind::TypeAlias,
2466 range: node_range_with_decorators(&def_node, source, lang),
2467 signature: Some(extract_signature(source, &def_node)),
2468 scope_chain: vec![],
2469 exported: false,
2470 parent: None,
2471 });
2472 }
2473
2474 if let (Some(name_node), Some(def_node)) = (const_name_node, const_def_node) {
2475 let signature = extract_signature(source, &def_node);
2476 let is_container = signature.contains("= struct")
2477 || signature.contains("= enum")
2478 || signature.contains("= union")
2479 || signature.contains("= opaque");
2480 let is_const = signature.trim_start().starts_with("const ");
2481 let name = node_text(source, &name_node).to_string();
2482 let already_captured = symbols.iter().any(|symbol| symbol.name == name);
2483 if is_const && !is_container && !already_captured {
2484 symbols.push(Symbol {
2485 name,
2486 kind: SymbolKind::Variable,
2487 range: node_range_with_decorators(&def_node, source, lang),
2488 signature: Some(signature),
2489 scope_chain: vec![],
2490 exported: false,
2491 parent: None,
2492 });
2493 }
2494 }
2495
2496 if let (Some(name_node), Some(def_node)) = (test_name_node, test_def_node) {
2497 let scope_chain = zig_container_scope_chain(&def_node, source);
2498 symbols.push(Symbol {
2499 name: node_text(source, &name_node).trim_matches('"').to_string(),
2500 kind: SymbolKind::Function,
2501 range: node_range_with_decorators(&def_node, source, lang),
2502 signature: Some(extract_signature(source, &def_node)),
2503 scope_chain: scope_chain.clone(),
2504 exported: false,
2505 parent: scope_chain.last().cloned(),
2506 });
2507 }
2508 }
2509
2510 dedup_symbols(&mut symbols);
2511 Ok(symbols)
2512}
2513
2514fn extract_csharp_symbols(
2516 source: &str,
2517 root: &Node,
2518 query: &Query,
2519) -> Result<Vec<Symbol>, AftError> {
2520 let lang = LangId::CSharp;
2521 let capture_names = query.capture_names();
2522
2523 let mut symbols = Vec::new();
2524 let mut cursor = QueryCursor::new();
2525 let mut matches = cursor.matches(query, *root, source.as_bytes());
2526
2527 while let Some(m) = {
2528 matches.advance();
2529 matches.get()
2530 } {
2531 let mut class_name_node = None;
2532 let mut class_def_node = None;
2533 let mut interface_name_node = None;
2534 let mut interface_def_node = None;
2535 let mut struct_name_node = None;
2536 let mut struct_def_node = None;
2537 let mut enum_name_node = None;
2538 let mut enum_def_node = None;
2539 let mut method_name_node = None;
2540 let mut method_def_node = None;
2541 let mut property_name_node = None;
2542 let mut property_def_node = None;
2543 let mut namespace_name_node = None;
2544 let mut namespace_def_node = None;
2545
2546 for cap in m.captures {
2547 let Some(&name) = capture_names.get(cap.index as usize) else {
2548 continue;
2549 };
2550 match name {
2551 "class.name" => class_name_node = Some(cap.node),
2552 "class.def" => class_def_node = Some(cap.node),
2553 "interface.name" => interface_name_node = Some(cap.node),
2554 "interface.def" => interface_def_node = Some(cap.node),
2555 "struct.name" => struct_name_node = Some(cap.node),
2556 "struct.def" => struct_def_node = Some(cap.node),
2557 "enum.name" => enum_name_node = Some(cap.node),
2558 "enum.def" => enum_def_node = Some(cap.node),
2559 "method.name" => method_name_node = Some(cap.node),
2560 "method.def" => method_def_node = Some(cap.node),
2561 "property.name" => property_name_node = Some(cap.node),
2562 "property.def" => property_def_node = Some(cap.node),
2563 "namespace.name" => namespace_name_node = Some(cap.node),
2564 "namespace.def" => namespace_def_node = Some(cap.node),
2565 _ => {}
2566 }
2567 }
2568
2569 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
2570 let scope_chain = csharp_scope_chain(&def_node, source);
2571 symbols.push(Symbol {
2572 name: node_text(source, &name_node).to_string(),
2573 kind: SymbolKind::Class,
2574 range: node_range_with_decorators(&def_node, source, lang),
2575 signature: Some(extract_signature(source, &def_node)),
2576 scope_chain: scope_chain.clone(),
2577 exported: false,
2578 parent: scope_chain.last().cloned(),
2579 });
2580 }
2581
2582 if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
2583 let scope_chain = csharp_scope_chain(&def_node, source);
2584 symbols.push(Symbol {
2585 name: node_text(source, &name_node).to_string(),
2586 kind: SymbolKind::Interface,
2587 range: node_range_with_decorators(&def_node, source, lang),
2588 signature: Some(extract_signature(source, &def_node)),
2589 scope_chain: scope_chain.clone(),
2590 exported: false,
2591 parent: scope_chain.last().cloned(),
2592 });
2593 }
2594
2595 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
2596 let scope_chain = csharp_scope_chain(&def_node, source);
2597 symbols.push(Symbol {
2598 name: node_text(source, &name_node).to_string(),
2599 kind: SymbolKind::Struct,
2600 range: node_range_with_decorators(&def_node, source, lang),
2601 signature: Some(extract_signature(source, &def_node)),
2602 scope_chain: scope_chain.clone(),
2603 exported: false,
2604 parent: scope_chain.last().cloned(),
2605 });
2606 }
2607
2608 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
2609 let scope_chain = csharp_scope_chain(&def_node, source);
2610 symbols.push(Symbol {
2611 name: node_text(source, &name_node).to_string(),
2612 kind: SymbolKind::Enum,
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)) = (method_name_node, method_def_node) {
2622 let scope_chain = csharp_scope_chain(&def_node, source);
2623 symbols.push(Symbol {
2624 name: node_text(source, &name_node).to_string(),
2625 kind: SymbolKind::Method,
2626 range: node_range_with_decorators(&def_node, source, lang),
2627 signature: Some(extract_signature(source, &def_node)),
2628 scope_chain: scope_chain.clone(),
2629 exported: false,
2630 parent: scope_chain.last().cloned(),
2631 });
2632 }
2633
2634 if let (Some(name_node), Some(def_node)) = (property_name_node, property_def_node) {
2635 let scope_chain = csharp_scope_chain(&def_node, source);
2636 symbols.push(Symbol {
2637 name: node_text(source, &name_node).to_string(),
2638 kind: SymbolKind::Variable,
2639 range: node_range_with_decorators(&def_node, source, lang),
2640 signature: Some(extract_signature(source, &def_node)),
2641 scope_chain: scope_chain.clone(),
2642 exported: false,
2643 parent: scope_chain.last().cloned(),
2644 });
2645 }
2646
2647 if let (Some(name_node), Some(def_node)) = (namespace_name_node, namespace_def_node) {
2648 let scope_chain = csharp_scope_chain(&def_node, source);
2649 symbols.push(Symbol {
2650 name: node_text(source, &name_node).to_string(),
2651 kind: SymbolKind::TypeAlias,
2652 range: node_range_with_decorators(&def_node, source, lang),
2653 signature: Some(extract_signature(source, &def_node)),
2654 scope_chain: scope_chain.clone(),
2655 exported: false,
2656 parent: scope_chain.last().cloned(),
2657 });
2658 }
2659 }
2660
2661 dedup_symbols(&mut symbols);
2662 Ok(symbols)
2663}
2664
2665fn find_type_identifier_recursive(node: &Node, source: &str) -> Option<String> {
2667 if node.kind() == "type_identifier" {
2668 return Some(node_text(source, node).to_string());
2669 }
2670 let mut cursor = node.walk();
2671 if cursor.goto_first_child() {
2672 loop {
2673 if let Some(result) = find_type_identifier_recursive(&cursor.node(), source) {
2674 return Some(result);
2675 }
2676 if !cursor.goto_next_sibling() {
2677 break;
2678 }
2679 }
2680 }
2681 None
2682}
2683
2684fn extract_bash_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2688 let lang = LangId::Bash;
2689 let capture_names = query.capture_names();
2690
2691 let mut symbols = Vec::new();
2692 let mut cursor = QueryCursor::new();
2693 let mut matches = cursor.matches(query, *root, source.as_bytes());
2694
2695 while let Some(m) = {
2696 matches.advance();
2697 matches.get()
2698 } {
2699 let mut fn_name_node = None;
2700 let mut fn_def_node = None;
2701
2702 for cap in m.captures {
2703 let Some(&name) = capture_names.get(cap.index as usize) else {
2704 continue;
2705 };
2706 match name {
2707 "fn.name" => fn_name_node = Some(cap.node),
2708 "fn.def" => fn_def_node = Some(cap.node),
2709 _ => {}
2710 }
2711 }
2712
2713 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2714 symbols.push(Symbol {
2715 name: node_text(source, &name_node).to_string(),
2716 kind: SymbolKind::Function,
2717 range: node_range_with_decorators(&def_node, source, lang),
2718 signature: Some(extract_signature(source, &def_node)),
2719 scope_chain: vec![],
2720 exported: false,
2721 parent: None,
2722 });
2723 }
2724 }
2725
2726 Ok(symbols)
2727}
2728
2729fn extract_html_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
2730 let mut headings: Vec<(u8, Symbol)> = Vec::new();
2731 collect_html_headings(source, root, &mut headings);
2732
2733 let total_lines = source.lines().count() as u32;
2734
2735 for i in 0..headings.len() {
2739 let level = headings[i].0;
2740 let section_end = headings[i + 1..]
2741 .iter()
2742 .find(|(l, _)| *l <= level)
2743 .map(|(_, s)| s.range.start_line.saturating_sub(1))
2744 .unwrap_or_else(|| total_lines.saturating_sub(1));
2745 headings[i].1.range.end_line = section_end;
2746 if section_end != headings[i].1.range.start_line {
2747 headings[i].1.range.end_col = 0;
2748 }
2749 }
2750
2751 let mut scope_stack: Vec<(u8, String)> = Vec::new(); for (level, symbol) in headings.iter_mut() {
2754 while scope_stack.last().is_some_and(|(l, _)| *l >= *level) {
2756 scope_stack.pop();
2757 }
2758 symbol.scope_chain = scope_stack.iter().map(|(_, name)| name.clone()).collect();
2759 symbol.parent = scope_stack.last().map(|(_, name)| name.clone());
2760 scope_stack.push((*level, symbol.name.clone()));
2761 }
2762
2763 Ok(headings.into_iter().map(|(_, s)| s).collect())
2764}
2765
2766fn collect_html_headings(source: &str, node: &Node, headings: &mut Vec<(u8, Symbol)>) {
2768 let mut cursor = node.walk();
2769 if !cursor.goto_first_child() {
2770 return;
2771 }
2772
2773 loop {
2774 let child = cursor.node();
2775 if child.kind() == "element" {
2776 if let Some(start_tag) = child
2778 .child_by_field_name("start_tag")
2779 .or_else(|| child.child(0).filter(|c| c.kind() == "start_tag"))
2780 {
2781 if let Some(tag_name_node) = start_tag
2782 .child_by_field_name("tag_name")
2783 .or_else(|| start_tag.child(1).filter(|c| c.kind() == "tag_name"))
2784 {
2785 let tag_name = node_text(source, &tag_name_node).to_lowercase();
2786 if let Some(level) = match tag_name.as_str() {
2787 "h1" => Some(1u8),
2788 "h2" => Some(2),
2789 "h3" => Some(3),
2790 "h4" => Some(4),
2791 "h5" => Some(5),
2792 "h6" => Some(6),
2793 _ => None,
2794 } {
2795 let text = extract_element_text(source, &child).trim().to_string();
2797 if !text.is_empty() {
2798 let range = node_range(&child);
2799 let signature = format!("<h{}> {}", level, text);
2800 headings.push((
2801 level,
2802 Symbol {
2803 name: text,
2804 kind: SymbolKind::Heading,
2805 range,
2806 signature: Some(signature),
2807 scope_chain: vec![], exported: false,
2809 parent: None, },
2811 ));
2812 }
2813 }
2814 }
2815 }
2816 collect_html_headings(source, &child, headings);
2818 } else {
2819 collect_html_headings(source, &child, headings);
2821 }
2822
2823 if !cursor.goto_next_sibling() {
2824 break;
2825 }
2826 }
2827}
2828
2829fn extract_element_text(source: &str, node: &Node) -> String {
2831 let mut text = String::new();
2832 let mut cursor = node.walk();
2833 if !cursor.goto_first_child() {
2834 return text;
2835 }
2836 loop {
2837 let child = cursor.node();
2838 match child.kind() {
2839 "text" => {
2840 text.push_str(node_text(source, &child));
2841 }
2842 "element" => {
2843 text.push_str(&extract_element_text(source, &child));
2845 }
2846 _ => {}
2847 }
2848 if !cursor.goto_next_sibling() {
2849 break;
2850 }
2851 }
2852 text
2853}
2854
2855fn extract_md_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
2859 let mut symbols = Vec::new();
2860 extract_md_sections(source, root, &mut symbols, &[]);
2861 Ok(symbols)
2862}
2863
2864fn extract_md_sections(
2866 source: &str,
2867 node: &Node,
2868 symbols: &mut Vec<Symbol>,
2869 scope_chain: &[String],
2870) {
2871 let mut cursor = node.walk();
2872 if !cursor.goto_first_child() {
2873 return;
2874 }
2875
2876 loop {
2877 let child = cursor.node();
2878 match child.kind() {
2879 "section" => {
2880 let mut section_cursor = child.walk();
2883 let mut heading_name = String::new();
2884 let mut heading_level: u8 = 0;
2885
2886 if section_cursor.goto_first_child() {
2887 loop {
2888 let section_child = section_cursor.node();
2889 if section_child.kind() == "atx_heading" {
2890 let mut h_cursor = section_child.walk();
2892 if h_cursor.goto_first_child() {
2893 loop {
2894 let h_child = h_cursor.node();
2895 let kind = h_child.kind();
2896 if kind.starts_with("atx_h") && kind.ends_with("_marker") {
2897 heading_level = kind
2899 .strip_prefix("atx_h")
2900 .and_then(|s| s.strip_suffix("_marker"))
2901 .and_then(|s| s.parse::<u8>().ok())
2902 .unwrap_or(1);
2903 } else if h_child.kind() == "inline" {
2904 heading_name =
2905 node_text(source, &h_child).trim().to_string();
2906 }
2907 if !h_cursor.goto_next_sibling() {
2908 break;
2909 }
2910 }
2911 }
2912 }
2913 if !section_cursor.goto_next_sibling() {
2914 break;
2915 }
2916 }
2917 }
2918
2919 if !heading_name.is_empty() {
2920 let range = node_range(&child);
2921 let signature = format!(
2922 "{} {}",
2923 "#".repeat((heading_level as usize).min(6)),
2924 heading_name
2925 );
2926
2927 symbols.push(Symbol {
2928 name: heading_name.clone(),
2929 kind: SymbolKind::Heading,
2930 range,
2931 signature: Some(signature),
2932 scope_chain: scope_chain.to_vec(),
2933 exported: false,
2934 parent: scope_chain.last().cloned(),
2935 });
2936
2937 let mut new_scope = scope_chain.to_vec();
2939 new_scope.push(heading_name);
2940 extract_md_sections(source, &child, symbols, &new_scope);
2941 }
2942 }
2943 _ => {}
2944 }
2945
2946 if !cursor.goto_next_sibling() {
2947 break;
2948 }
2949 }
2950}
2951
2952fn dedup_symbols(symbols: &mut Vec<Symbol>) {
2956 let mut seen = std::collections::HashSet::new();
2957 symbols.retain(|s| {
2958 let key = (s.name.clone(), format!("{:?}", s.kind), s.range.start_line);
2959 seen.insert(key)
2960 });
2961}
2962
2963pub struct TreeSitterProvider {
2966 parser: RefCell<FileParser>,
2967}
2968
2969#[derive(Debug, Clone)]
2970struct ReExportTarget {
2971 file: PathBuf,
2972 symbol_name: String,
2973}
2974
2975impl TreeSitterProvider {
2976 pub fn new() -> Self {
2978 Self::with_symbol_cache(Arc::new(RwLock::new(SymbolCache::new())))
2979 }
2980
2981 pub fn with_symbol_cache(symbol_cache: SharedSymbolCache) -> Self {
2983 Self {
2984 parser: RefCell::new(FileParser::with_symbol_cache(symbol_cache)),
2985 }
2986 }
2987
2988 pub fn symbol_cache_len(&self) -> usize {
2990 let parser = self.parser.borrow();
2991 parser.symbol_cache_len()
2992 }
2993
2994 pub fn symbol_cache(&self) -> SharedSymbolCache {
2996 let parser = self.parser.borrow();
2997 parser.symbol_cache()
2998 }
2999
3000 fn resolve_symbol_inner(
3001 &self,
3002 file: &Path,
3003 name: &str,
3004 depth: usize,
3005 visited: &mut HashSet<(PathBuf, String)>,
3006 ) -> Result<Vec<SymbolMatch>, AftError> {
3007 if depth > MAX_REEXPORT_DEPTH {
3008 return Ok(Vec::new());
3009 }
3010
3011 let canonical_file = std::fs::canonicalize(file).unwrap_or_else(|_| file.to_path_buf());
3012 if !visited.insert((canonical_file, name.to_string())) {
3013 return Ok(Vec::new());
3014 }
3015
3016 let symbols = self.parser.borrow_mut().extract_symbols(file)?;
3017 let local_matches = symbol_matches_in_file(file, &symbols, name);
3018 if !local_matches.is_empty() {
3019 return Ok(local_matches);
3020 }
3021
3022 if name == "default" {
3023 let default_matches = self.resolve_local_default_export(file, &symbols)?;
3024 if !default_matches.is_empty() {
3025 return Ok(default_matches);
3026 }
3027 }
3028
3029 let reexport_targets = self.collect_reexport_targets(file, name)?;
3030 let mut matches = Vec::new();
3031 let mut seen = HashSet::new();
3032 for target in reexport_targets {
3033 for resolved in
3034 self.resolve_symbol_inner(&target.file, &target.symbol_name, depth + 1, visited)?
3035 {
3036 let key = format!(
3037 "{}:{}:{}:{}:{}:{}",
3038 resolved.file,
3039 resolved.symbol.name,
3040 resolved.symbol.range.start_line,
3041 resolved.symbol.range.start_col,
3042 resolved.symbol.range.end_line,
3043 resolved.symbol.range.end_col
3044 );
3045 if seen.insert(key) {
3046 matches.push(resolved);
3047 }
3048 }
3049 }
3050
3051 Ok(matches)
3052 }
3053
3054 fn collect_reexport_targets(
3055 &self,
3056 file: &Path,
3057 requested_name: &str,
3058 ) -> Result<Vec<ReExportTarget>, AftError> {
3059 let (source, tree, lang) = self.read_parsed_file(file)?;
3060 if !matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
3061 return Ok(Vec::new());
3062 }
3063
3064 let mut targets = Vec::new();
3065 let root = tree.root_node();
3066 let from_dir = file.parent().unwrap_or_else(|| Path::new("."));
3067
3068 let mut cursor = root.walk();
3069 if !cursor.goto_first_child() {
3070 return Ok(targets);
3071 }
3072
3073 loop {
3074 let node = cursor.node();
3075 if node.kind() == "export_statement" {
3076 let Some(source_node) = node.child_by_field_name("source") else {
3077 if !cursor.goto_next_sibling() {
3078 break;
3079 }
3080 continue;
3081 };
3082
3083 let Some(module_path) = string_content(&source, &source_node) else {
3084 if !cursor.goto_next_sibling() {
3085 break;
3086 }
3087 continue;
3088 };
3089
3090 let Some(target_file) = resolve_module_path(from_dir, &module_path) else {
3091 if !cursor.goto_next_sibling() {
3092 break;
3093 }
3094 continue;
3095 };
3096
3097 if let Some(export_clause) = find_child_by_kind(node, "export_clause") {
3098 if let Some(symbol_name) =
3099 resolve_export_clause_name(&source, &export_clause, requested_name)
3100 {
3101 targets.push(ReExportTarget {
3102 file: target_file,
3103 symbol_name,
3104 });
3105 }
3106 } else if export_statement_has_wildcard(&source, &node) {
3107 targets.push(ReExportTarget {
3108 file: target_file,
3109 symbol_name: requested_name.to_string(),
3110 });
3111 }
3112 }
3113
3114 if !cursor.goto_next_sibling() {
3115 break;
3116 }
3117 }
3118
3119 Ok(targets)
3120 }
3121
3122 fn resolve_local_default_export(
3123 &self,
3124 file: &Path,
3125 symbols: &[Symbol],
3126 ) -> Result<Vec<SymbolMatch>, AftError> {
3127 let (source, tree, lang) = self.read_parsed_file(file)?;
3128 if !matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
3129 return Ok(Vec::new());
3130 }
3131
3132 let root = tree.root_node();
3133 let mut matches = Vec::new();
3134 let mut seen = HashSet::new();
3135
3136 let mut cursor = root.walk();
3137 if !cursor.goto_first_child() {
3138 return Ok(matches);
3139 }
3140
3141 loop {
3142 let node = cursor.node();
3143 if node.kind() == "export_statement"
3144 && node.child_by_field_name("source").is_none()
3145 && node_contains_token(&source, &node, "default")
3146 {
3147 if let Some(target_name) = default_export_target_name(&source, &node) {
3148 for symbol_match in symbol_matches_in_file(file, symbols, &target_name) {
3149 let key = format!(
3150 "{}:{}:{}:{}:{}:{}",
3151 symbol_match.file,
3152 symbol_match.symbol.name,
3153 symbol_match.symbol.range.start_line,
3154 symbol_match.symbol.range.start_col,
3155 symbol_match.symbol.range.end_line,
3156 symbol_match.symbol.range.end_col
3157 );
3158 if seen.insert(key) {
3159 matches.push(symbol_match);
3160 }
3161 }
3162 }
3163 }
3164
3165 if !cursor.goto_next_sibling() {
3166 break;
3167 }
3168 }
3169
3170 Ok(matches)
3171 }
3172
3173 fn read_parsed_file(&self, file: &Path) -> Result<(String, Tree, LangId), AftError> {
3174 let source = std::fs::read_to_string(file).map_err(|e| AftError::FileNotFound {
3175 path: format!("{}: {}", file.display(), e),
3176 })?;
3177 let (tree, lang) = {
3178 let mut parser = self.parser.borrow_mut();
3179 parser.parse_cloned(file)?
3180 };
3181 Ok((source, tree, lang))
3182 }
3183}
3184
3185fn symbol_matches_in_file(file: &Path, symbols: &[Symbol], name: &str) -> Vec<SymbolMatch> {
3186 symbols
3187 .iter()
3188 .filter(|symbol| symbol.name == name)
3189 .cloned()
3190 .map(|symbol| SymbolMatch {
3191 file: file.display().to_string(),
3192 symbol,
3193 })
3194 .collect()
3195}
3196
3197fn string_content(source: &str, node: &Node) -> Option<String> {
3198 let text = node_text(source, node);
3199 if text.len() < 2 {
3200 return None;
3201 }
3202
3203 Some(
3204 text.trim_start_matches(|c| c == '\'' || c == '"')
3205 .trim_end_matches(|c| c == '\'' || c == '"')
3206 .to_string(),
3207 )
3208}
3209
3210fn find_child_by_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
3211 let mut cursor = node.walk();
3212 if !cursor.goto_first_child() {
3213 return None;
3214 }
3215
3216 loop {
3217 let child = cursor.node();
3218 if child.kind() == kind {
3219 return Some(child);
3220 }
3221 if !cursor.goto_next_sibling() {
3222 break;
3223 }
3224 }
3225
3226 None
3227}
3228
3229fn resolve_export_clause_name(
3230 source: &str,
3231 export_clause: &Node,
3232 requested_name: &str,
3233) -> Option<String> {
3234 let mut cursor = export_clause.walk();
3235 if !cursor.goto_first_child() {
3236 return None;
3237 }
3238
3239 loop {
3240 let child = cursor.node();
3241 if child.kind() == "export_specifier" {
3242 let (source_name, exported_name) = export_specifier_names(source, &child)?;
3243 if exported_name == requested_name {
3244 return Some(source_name);
3245 }
3246 }
3247
3248 if !cursor.goto_next_sibling() {
3249 break;
3250 }
3251 }
3252
3253 None
3254}
3255
3256fn export_specifier_names(source: &str, specifier: &Node) -> Option<(String, String)> {
3257 let source_name = specifier
3258 .child_by_field_name("name")
3259 .map(|node| node_text(source, &node).to_string());
3260 let alias_name = specifier
3261 .child_by_field_name("alias")
3262 .map(|node| node_text(source, &node).to_string());
3263
3264 if let Some(source_name) = source_name {
3265 let exported_name = alias_name.unwrap_or_else(|| source_name.clone());
3266 return Some((source_name, exported_name));
3267 }
3268
3269 let mut names = Vec::new();
3270 let mut cursor = specifier.walk();
3271 if cursor.goto_first_child() {
3272 loop {
3273 let child = cursor.node();
3274 let child_text = node_text(source, &child).trim();
3275 if matches!(
3276 child.kind(),
3277 "identifier" | "type_identifier" | "property_identifier"
3278 ) || child_text == "default"
3279 {
3280 names.push(child_text.to_string());
3281 }
3282 if !cursor.goto_next_sibling() {
3283 break;
3284 }
3285 }
3286 }
3287
3288 match names.as_slice() {
3289 [name] => Some((name.clone(), name.clone())),
3290 [source_name, exported_name, ..] => Some((source_name.clone(), exported_name.clone())),
3291 _ => None,
3292 }
3293}
3294
3295fn export_statement_has_wildcard(source: &str, node: &Node) -> bool {
3296 let mut cursor = node.walk();
3297 if !cursor.goto_first_child() {
3298 return false;
3299 }
3300
3301 loop {
3302 if node_text(source, &cursor.node()).trim() == "*" {
3303 return true;
3304 }
3305 if !cursor.goto_next_sibling() {
3306 break;
3307 }
3308 }
3309
3310 false
3311}
3312
3313fn node_contains_token(source: &str, node: &Node, token: &str) -> bool {
3314 let mut cursor = node.walk();
3315 if !cursor.goto_first_child() {
3316 return false;
3317 }
3318
3319 loop {
3320 if node_text(source, &cursor.node()).trim() == token {
3321 return true;
3322 }
3323 if !cursor.goto_next_sibling() {
3324 break;
3325 }
3326 }
3327
3328 false
3329}
3330
3331fn default_export_target_name(source: &str, export_stmt: &Node) -> Option<String> {
3332 let mut cursor = export_stmt.walk();
3333 if !cursor.goto_first_child() {
3334 return None;
3335 }
3336
3337 loop {
3338 let child = cursor.node();
3339 match child.kind() {
3340 "function_declaration"
3341 | "class_declaration"
3342 | "interface_declaration"
3343 | "enum_declaration"
3344 | "type_alias_declaration"
3345 | "lexical_declaration" => {
3346 if let Some(name_node) = child.child_by_field_name("name") {
3347 return Some(node_text(source, &name_node).to_string());
3348 }
3349
3350 if child.kind() == "lexical_declaration" {
3351 let mut child_cursor = child.walk();
3352 if child_cursor.goto_first_child() {
3353 loop {
3354 let nested = child_cursor.node();
3355 if nested.kind() == "variable_declarator" {
3356 if let Some(name_node) = nested.child_by_field_name("name") {
3357 return Some(node_text(source, &name_node).to_string());
3358 }
3359 }
3360 if !child_cursor.goto_next_sibling() {
3361 break;
3362 }
3363 }
3364 }
3365 }
3366 }
3367 "identifier" | "type_identifier" => {
3368 let text = node_text(source, &child);
3369 if text != "export" && text != "default" {
3370 return Some(text.to_string());
3371 }
3372 }
3373 _ => {}
3374 }
3375
3376 if !cursor.goto_next_sibling() {
3377 break;
3378 }
3379 }
3380
3381 None
3382}
3383
3384impl crate::language::LanguageProvider for TreeSitterProvider {
3385 fn resolve_symbol(&self, file: &Path, name: &str) -> Result<Vec<SymbolMatch>, AftError> {
3386 let matches = self.resolve_symbol_inner(file, name, 0, &mut HashSet::new())?;
3387
3388 if matches.is_empty() {
3389 Err(AftError::SymbolNotFound {
3390 name: name.to_string(),
3391 file: file.display().to_string(),
3392 })
3393 } else {
3394 Ok(matches)
3395 }
3396 }
3397
3398 fn list_symbols(&self, file: &Path) -> Result<Vec<Symbol>, AftError> {
3399 self.parser.borrow_mut().extract_symbols(file)
3400 }
3401
3402 fn as_any(&self) -> &dyn std::any::Any {
3403 self
3404 }
3405}
3406
3407#[cfg(test)]
3408mod tests {
3409 use super::*;
3410 use crate::language::LanguageProvider;
3411 use crate::symbol_cache_disk;
3412 use std::path::PathBuf;
3413
3414 fn fixture_path(name: &str) -> PathBuf {
3415 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
3416 .join("tests")
3417 .join("fixtures")
3418 .join(name)
3419 }
3420
3421 fn test_symbol(name: &str) -> Symbol {
3422 Symbol {
3423 name: name.to_string(),
3424 kind: SymbolKind::Function,
3425 range: Range {
3426 start_line: 0,
3427 start_col: 0,
3428 end_line: 0,
3429 end_col: 10,
3430 },
3431 signature: Some(format!("fn {name}()")),
3432 scope_chain: Vec::new(),
3433 exported: true,
3434 parent: None,
3435 }
3436 }
3437
3438 #[test]
3439 fn symbol_cache_load_from_disk_round_trips_synthetic_entry() {
3440 let project = tempfile::tempdir().expect("create project dir");
3441 let storage = tempfile::tempdir().expect("create storage dir");
3442 let source = project.path().join("src/lib.rs");
3443 std::fs::create_dir_all(source.parent().expect("source parent"))
3444 .expect("create source dir");
3445 std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
3446 let mtime = std::fs::metadata(&source)
3447 .expect("stat source")
3448 .modified()
3449 .expect("source mtime");
3450
3451 let mut cache = SymbolCache::new();
3452 cache.set_project_root(project.path().to_path_buf());
3453 cache.insert(source.clone(), mtime, vec![test_symbol("cached")]);
3454 symbol_cache_disk::write_to_disk(&cache, storage.path(), "unit-project")
3455 .expect("write symbol cache");
3456
3457 let mut restored = SymbolCache::new();
3458 let loaded = restored.load_from_disk(storage.path(), "unit-project");
3459 let symbols = restored.get(&source, mtime).expect("restored symbols");
3460
3461 assert_eq!(loaded, 1);
3462 assert_eq!(symbols.len(), 1);
3463 assert_eq!(symbols[0].name, "cached");
3464 }
3465
3466 #[test]
3467 fn symbol_cache_load_from_disk_drops_stale_synthetic_entry() {
3468 let project = tempfile::tempdir().expect("create project dir");
3469 let storage = tempfile::tempdir().expect("create storage dir");
3470 let source = project.path().join("src/lib.rs");
3471 std::fs::create_dir_all(source.parent().expect("source parent"))
3472 .expect("create source dir");
3473 std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
3474 let mtime = std::fs::metadata(&source)
3475 .expect("stat source")
3476 .modified()
3477 .expect("source mtime");
3478
3479 let mut cache = SymbolCache::new();
3480 cache.set_project_root(project.path().to_path_buf());
3481 cache.insert(source.clone(), mtime, vec![test_symbol("cached")]);
3482 symbol_cache_disk::write_to_disk(&cache, storage.path(), "stale-unit-project")
3483 .expect("write symbol cache");
3484
3485 std::fs::write(&source, "pub fn cached() {}\npub fn fresh() {}\n").expect("change source");
3486
3487 let mut restored = SymbolCache::new();
3488 let loaded = restored.load_from_disk(storage.path(), "stale-unit-project");
3489
3490 assert_eq!(loaded, 0);
3491 assert_eq!(restored.len(), 0);
3492 }
3493
3494 #[test]
3495 fn stale_prewarm_generation_cannot_repopulate_symbol_cache_after_reset() {
3496 let project = tempfile::tempdir().expect("create project dir");
3497 let storage = tempfile::tempdir().expect("create storage dir");
3498 let source = project.path().join("src/lib.rs");
3499 std::fs::create_dir_all(source.parent().expect("source parent"))
3500 .expect("create source dir");
3501 std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
3502 let mtime = std::fs::metadata(&source)
3503 .expect("stat source")
3504 .modified()
3505 .expect("source mtime");
3506
3507 let mut disk_cache = SymbolCache::new();
3508 disk_cache.set_project_root(project.path().to_path_buf());
3509 disk_cache.insert(source.clone(), mtime, vec![test_symbol("cached")]);
3510 symbol_cache_disk::write_to_disk(&disk_cache, storage.path(), "prewarm-reset")
3511 .expect("write symbol cache");
3512
3513 let shared = Arc::new(RwLock::new(SymbolCache::new()));
3514 let stale_generation = shared.write().unwrap().reset();
3515 let active_generation = shared.write().unwrap().reset();
3516 assert_ne!(stale_generation, active_generation);
3517
3518 {
3519 let mut cache = shared.write().unwrap();
3520 assert!(!cache
3521 .set_project_root_for_generation(stale_generation, project.path().to_path_buf()));
3522 assert_eq!(
3523 cache.load_from_disk_for_generation(
3524 stale_generation,
3525 storage.path(),
3526 "prewarm-reset"
3527 ),
3528 0
3529 );
3530 }
3531
3532 let mut stale_parser =
3533 FileParser::with_symbol_cache_generation(Arc::clone(&shared), Some(stale_generation));
3534 stale_parser
3535 .extract_symbols(&source)
3536 .expect("stale prewarm parses source but must not write cache");
3537
3538 let cache = shared.read().unwrap();
3539 assert_eq!(cache.generation(), active_generation);
3540 assert_eq!(cache.len(), 0);
3541 assert!(cache.project_root().is_none());
3542 assert!(!cache.contains_key(&source));
3543 }
3544
3545 #[test]
3548 fn detect_ts() {
3549 assert_eq!(
3550 detect_language(Path::new("foo.ts")),
3551 Some(LangId::TypeScript)
3552 );
3553 }
3554
3555 #[test]
3556 fn detect_tsx() {
3557 assert_eq!(detect_language(Path::new("foo.tsx")), Some(LangId::Tsx));
3558 }
3559
3560 #[test]
3561 fn detect_js() {
3562 assert_eq!(
3563 detect_language(Path::new("foo.js")),
3564 Some(LangId::JavaScript)
3565 );
3566 }
3567
3568 #[test]
3569 fn detect_jsx() {
3570 assert_eq!(
3571 detect_language(Path::new("foo.jsx")),
3572 Some(LangId::JavaScript)
3573 );
3574 }
3575
3576 #[test]
3577 fn detect_py() {
3578 assert_eq!(detect_language(Path::new("foo.py")), Some(LangId::Python));
3579 }
3580
3581 #[test]
3582 fn detect_rs() {
3583 assert_eq!(detect_language(Path::new("foo.rs")), Some(LangId::Rust));
3584 }
3585
3586 #[test]
3587 fn detect_go() {
3588 assert_eq!(detect_language(Path::new("foo.go")), Some(LangId::Go));
3589 }
3590
3591 #[test]
3592 fn detect_c() {
3593 assert_eq!(detect_language(Path::new("foo.c")), Some(LangId::C));
3594 }
3595
3596 #[test]
3597 fn detect_h() {
3598 assert_eq!(detect_language(Path::new("foo.h")), Some(LangId::C));
3599 }
3600
3601 #[test]
3602 fn detect_cc() {
3603 assert_eq!(detect_language(Path::new("foo.cc")), Some(LangId::Cpp));
3604 }
3605
3606 #[test]
3607 fn detect_cpp() {
3608 assert_eq!(detect_language(Path::new("foo.cpp")), Some(LangId::Cpp));
3609 }
3610
3611 #[test]
3612 fn detect_cxx() {
3613 assert_eq!(detect_language(Path::new("foo.cxx")), Some(LangId::Cpp));
3614 }
3615
3616 #[test]
3617 fn detect_hpp() {
3618 assert_eq!(detect_language(Path::new("foo.hpp")), Some(LangId::Cpp));
3619 }
3620
3621 #[test]
3622 fn detect_hh() {
3623 assert_eq!(detect_language(Path::new("foo.hh")), Some(LangId::Cpp));
3624 }
3625
3626 #[test]
3627 fn detect_zig() {
3628 assert_eq!(detect_language(Path::new("foo.zig")), Some(LangId::Zig));
3629 }
3630
3631 #[test]
3632 fn detect_cs() {
3633 assert_eq!(detect_language(Path::new("foo.cs")), Some(LangId::CSharp));
3634 }
3635
3636 #[test]
3637 fn detect_unknown_returns_none() {
3638 assert_eq!(detect_language(Path::new("foo.txt")), None);
3639 }
3640
3641 #[test]
3644 fn unsupported_extension_returns_invalid_request() {
3645 let path = fixture_path("sample.ts");
3647 let bad_path = path.with_extension("txt");
3648 std::fs::write(&bad_path, "hello").unwrap();
3650 let provider = TreeSitterProvider::new();
3651 let result = provider.list_symbols(&bad_path);
3652 std::fs::remove_file(&bad_path).ok();
3653 match result {
3654 Err(AftError::InvalidRequest { message }) => {
3655 assert!(
3656 message.contains("unsupported file extension"),
3657 "msg: {}",
3658 message
3659 );
3660 assert!(message.contains("txt"), "msg: {}", message);
3661 }
3662 other => panic!("expected InvalidRequest, got {:?}", other),
3663 }
3664 }
3665
3666 #[test]
3669 fn ts_extracts_all_symbol_kinds() {
3670 let provider = TreeSitterProvider::new();
3671 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
3672
3673 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
3674 assert!(
3675 names.contains(&"greet"),
3676 "missing function greet: {:?}",
3677 names
3678 );
3679 assert!(names.contains(&"add"), "missing arrow fn add: {:?}", names);
3680 assert!(
3681 names.contains(&"UserService"),
3682 "missing class UserService: {:?}",
3683 names
3684 );
3685 assert!(
3686 names.contains(&"Config"),
3687 "missing interface Config: {:?}",
3688 names
3689 );
3690 assert!(
3691 names.contains(&"Status"),
3692 "missing enum Status: {:?}",
3693 names
3694 );
3695 assert!(
3696 names.contains(&"UserId"),
3697 "missing type alias UserId: {:?}",
3698 names
3699 );
3700 assert!(
3701 names.contains(&"internalHelper"),
3702 "missing non-exported fn: {:?}",
3703 names
3704 );
3705
3706 assert!(
3708 symbols.len() >= 6,
3709 "expected ≥6 symbols, got {}: {:?}",
3710 symbols.len(),
3711 names
3712 );
3713 }
3714
3715 #[test]
3716 fn ts_symbol_kinds_correct() {
3717 let provider = TreeSitterProvider::new();
3718 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
3719
3720 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
3721
3722 assert_eq!(find("greet").kind, SymbolKind::Function);
3723 assert_eq!(find("add").kind, SymbolKind::Function); assert_eq!(find("UserService").kind, SymbolKind::Class);
3725 assert_eq!(find("Config").kind, SymbolKind::Interface);
3726 assert_eq!(find("Status").kind, SymbolKind::Enum);
3727 assert_eq!(find("UserId").kind, SymbolKind::TypeAlias);
3728 }
3729
3730 #[test]
3731 fn ts_export_detection() {
3732 let provider = TreeSitterProvider::new();
3733 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
3734
3735 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
3736
3737 assert!(find("greet").exported, "greet should be exported");
3738 assert!(find("add").exported, "add should be exported");
3739 assert!(
3740 find("UserService").exported,
3741 "UserService should be exported"
3742 );
3743 assert!(find("Config").exported, "Config should be exported");
3744 assert!(find("Status").exported, "Status should be exported");
3745 assert!(
3746 !find("internalHelper").exported,
3747 "internalHelper should not be exported"
3748 );
3749 }
3750
3751 #[test]
3752 fn ts_method_scope_chain() {
3753 let provider = TreeSitterProvider::new();
3754 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
3755
3756 let methods: Vec<&Symbol> = symbols
3757 .iter()
3758 .filter(|s| s.kind == SymbolKind::Method)
3759 .collect();
3760 assert!(!methods.is_empty(), "should have at least one method");
3761
3762 for method in &methods {
3763 assert_eq!(
3764 method.scope_chain,
3765 vec!["UserService"],
3766 "method {} should have UserService in scope chain",
3767 method.name
3768 );
3769 assert_eq!(method.parent.as_deref(), Some("UserService"));
3770 }
3771 }
3772
3773 #[test]
3774 fn ts_signatures_present() {
3775 let provider = TreeSitterProvider::new();
3776 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
3777
3778 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
3779
3780 let greet_sig = find("greet").signature.as_ref().unwrap();
3781 assert!(
3782 greet_sig.contains("greet"),
3783 "signature should contain function name: {}",
3784 greet_sig
3785 );
3786 }
3787
3788 #[test]
3789 fn ts_ranges_valid() {
3790 let provider = TreeSitterProvider::new();
3791 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
3792
3793 for s in &symbols {
3794 assert!(
3795 s.range.end_line >= s.range.start_line,
3796 "symbol {} has invalid range: {:?}",
3797 s.name,
3798 s.range
3799 );
3800 }
3801 }
3802
3803 #[test]
3806 fn js_extracts_core_symbols() {
3807 let provider = TreeSitterProvider::new();
3808 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
3809
3810 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
3811 assert!(
3812 names.contains(&"multiply"),
3813 "missing function multiply: {:?}",
3814 names
3815 );
3816 assert!(
3817 names.contains(&"divide"),
3818 "missing arrow fn divide: {:?}",
3819 names
3820 );
3821 assert!(
3822 names.contains(&"EventEmitter"),
3823 "missing class EventEmitter: {:?}",
3824 names
3825 );
3826 assert!(
3827 names.contains(&"main"),
3828 "missing default export fn main: {:?}",
3829 names
3830 );
3831
3832 assert!(
3833 symbols.len() >= 4,
3834 "expected ≥4 symbols, got {}: {:?}",
3835 symbols.len(),
3836 names
3837 );
3838 }
3839
3840 #[test]
3841 fn js_arrow_fn_correctly_named() {
3842 let provider = TreeSitterProvider::new();
3843 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
3844
3845 let divide = symbols.iter().find(|s| s.name == "divide").unwrap();
3846 assert_eq!(divide.kind, SymbolKind::Function);
3847 assert!(divide.exported, "divide should be exported");
3848
3849 let internal = symbols.iter().find(|s| s.name == "internalUtil").unwrap();
3850 assert_eq!(internal.kind, SymbolKind::Function);
3851 assert!(!internal.exported, "internalUtil should not be exported");
3852 }
3853
3854 #[test]
3855 fn js_method_scope_chain() {
3856 let provider = TreeSitterProvider::new();
3857 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
3858
3859 let methods: Vec<&Symbol> = symbols
3860 .iter()
3861 .filter(|s| s.kind == SymbolKind::Method)
3862 .collect();
3863
3864 for method in &methods {
3865 assert_eq!(
3866 method.scope_chain,
3867 vec!["EventEmitter"],
3868 "method {} should have EventEmitter in scope chain",
3869 method.name
3870 );
3871 }
3872 }
3873
3874 #[test]
3877 fn tsx_extracts_react_component() {
3878 let provider = TreeSitterProvider::new();
3879 let symbols = provider.list_symbols(&fixture_path("sample.tsx")).unwrap();
3880
3881 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
3882 assert!(
3883 names.contains(&"Button"),
3884 "missing React component Button: {:?}",
3885 names
3886 );
3887 assert!(
3888 names.contains(&"Counter"),
3889 "missing class Counter: {:?}",
3890 names
3891 );
3892 assert!(
3893 names.contains(&"formatLabel"),
3894 "missing function formatLabel: {:?}",
3895 names
3896 );
3897
3898 assert!(
3899 symbols.len() >= 2,
3900 "expected ≥2 symbols, got {}: {:?}",
3901 symbols.len(),
3902 names
3903 );
3904 }
3905
3906 #[test]
3907 fn tsx_jsx_doesnt_break_parser() {
3908 let provider = TreeSitterProvider::new();
3910 let result = provider.list_symbols(&fixture_path("sample.tsx"));
3911 assert!(
3912 result.is_ok(),
3913 "TSX parsing should succeed: {:?}",
3914 result.err()
3915 );
3916 }
3917
3918 #[test]
3921 fn resolve_symbol_finds_match() {
3922 let provider = TreeSitterProvider::new();
3923 let matches = provider
3924 .resolve_symbol(&fixture_path("sample.ts"), "greet")
3925 .unwrap();
3926 assert_eq!(matches.len(), 1);
3927 assert_eq!(matches[0].symbol.name, "greet");
3928 assert_eq!(matches[0].symbol.kind, SymbolKind::Function);
3929 }
3930
3931 #[test]
3932 fn resolve_symbol_not_found() {
3933 let provider = TreeSitterProvider::new();
3934 let result = provider.resolve_symbol(&fixture_path("sample.ts"), "nonexistent");
3935 assert!(matches!(result, Err(AftError::SymbolNotFound { .. })));
3936 }
3937
3938 #[test]
3939 fn resolve_symbol_follows_reexport_chains() {
3940 let dir = tempfile::tempdir().unwrap();
3941 let config = dir.path().join("config.ts");
3942 let barrel1 = dir.path().join("barrel1.ts");
3943 let barrel2 = dir.path().join("barrel2.ts");
3944 let barrel3 = dir.path().join("barrel3.ts");
3945 let index = dir.path().join("index.ts");
3946
3947 std::fs::write(
3948 &config,
3949 "export class Config {}\nexport default class DefaultConfig {}\n",
3950 )
3951 .unwrap();
3952 std::fs::write(
3953 &barrel1,
3954 "export { Config } from './config';\nexport { default as NamedDefault } from './config';\n",
3955 )
3956 .unwrap();
3957 std::fs::write(
3958 &barrel2,
3959 "export { Config as RenamedConfig } from './barrel1';\n",
3960 )
3961 .unwrap();
3962 std::fs::write(
3963 &barrel3,
3964 "export * from './barrel2';\nexport * from './barrel1';\n",
3965 )
3966 .unwrap();
3967 std::fs::write(
3968 &index,
3969 "export class Config {}\nexport { RenamedConfig as FinalConfig } from './barrel3';\nexport * from './barrel3';\n",
3970 )
3971 .unwrap();
3972
3973 let provider = TreeSitterProvider::new();
3974 let config_canon = std::fs::canonicalize(&config).unwrap();
3975
3976 let direct = provider.resolve_symbol(&barrel1, "Config").unwrap();
3977 assert_eq!(direct.len(), 1);
3978 assert_eq!(direct[0].symbol.name, "Config");
3979 assert_eq!(direct[0].file, config_canon.display().to_string());
3980
3981 let renamed = provider.resolve_symbol(&barrel2, "RenamedConfig").unwrap();
3982 assert_eq!(renamed.len(), 1);
3983 assert_eq!(renamed[0].symbol.name, "Config");
3984 assert_eq!(renamed[0].file, config_canon.display().to_string());
3985
3986 let wildcard_chain = provider.resolve_symbol(&index, "FinalConfig").unwrap();
3987 assert_eq!(wildcard_chain.len(), 1);
3988 assert_eq!(wildcard_chain[0].symbol.name, "Config");
3989 assert_eq!(wildcard_chain[0].file, config_canon.display().to_string());
3990
3991 let wildcard_default = provider.resolve_symbol(&index, "NamedDefault").unwrap();
3992 assert_eq!(wildcard_default.len(), 1);
3993 assert_eq!(wildcard_default[0].symbol.name, "DefaultConfig");
3994 assert_eq!(wildcard_default[0].file, config_canon.display().to_string());
3995
3996 let local = provider.resolve_symbol(&index, "Config").unwrap();
3997 assert_eq!(local.len(), 1);
3998 assert_eq!(local[0].symbol.name, "Config");
3999 assert_eq!(local[0].file, index.display().to_string());
4000 }
4001
4002 #[test]
4005 fn symbol_range_includes_rust_attributes() {
4006 let dir = tempfile::tempdir().unwrap();
4007 let path = dir.path().join("test_attrs.rs");
4008 std::fs::write(
4009 &path,
4010 "/// This is a doc comment\n#[test]\n#[cfg(test)]\nfn my_test_fn() {\n assert!(true);\n}\n",
4011 )
4012 .unwrap();
4013
4014 let provider = TreeSitterProvider::new();
4015 let matches = provider.resolve_symbol(&path, "my_test_fn").unwrap();
4016 assert_eq!(matches.len(), 1);
4017 assert_eq!(
4018 matches[0].symbol.range.start_line, 0,
4019 "symbol range should include preceding /// doc comment, got start_line={}",
4020 matches[0].symbol.range.start_line
4021 );
4022 }
4023
4024 #[test]
4025 fn symbol_range_includes_go_doc_comment() {
4026 let dir = tempfile::tempdir().unwrap();
4027 let path = dir.path().join("test_doc.go");
4028 std::fs::write(
4029 &path,
4030 "package main\n\n// MyFunc does something useful.\n// It has a multi-line doc.\nfunc MyFunc() {\n}\n",
4031 )
4032 .unwrap();
4033
4034 let provider = TreeSitterProvider::new();
4035 let matches = provider.resolve_symbol(&path, "MyFunc").unwrap();
4036 assert_eq!(matches.len(), 1);
4037 assert_eq!(
4038 matches[0].symbol.range.start_line, 2,
4039 "symbol range should include preceding doc comments, got start_line={}",
4040 matches[0].symbol.range.start_line
4041 );
4042 }
4043
4044 #[test]
4045 fn symbol_range_skips_unrelated_comments() {
4046 let dir = tempfile::tempdir().unwrap();
4047 let path = dir.path().join("test_gap.go");
4048 std::fs::write(
4049 &path,
4050 "package main\n\n// This is a standalone comment\n\nfunc Standalone() {\n}\n",
4051 )
4052 .unwrap();
4053
4054 let provider = TreeSitterProvider::new();
4055 let matches = provider.resolve_symbol(&path, "Standalone").unwrap();
4056 assert_eq!(matches.len(), 1);
4057 assert_eq!(
4058 matches[0].symbol.range.start_line, 4,
4059 "symbol range should NOT include comment separated by blank line, got start_line={}",
4060 matches[0].symbol.range.start_line
4061 );
4062 }
4063
4064 #[test]
4065 fn parse_cache_returns_same_tree() {
4066 let mut parser = FileParser::new();
4067 let path = fixture_path("sample.ts");
4068
4069 let (tree1, _) = parser.parse(&path).unwrap();
4070 let tree1_root = tree1.root_node().byte_range();
4071
4072 let (tree2, _) = parser.parse(&path).unwrap();
4073 let tree2_root = tree2.root_node().byte_range();
4074
4075 assert_eq!(tree1_root, tree2_root);
4077 }
4078
4079 #[test]
4080 fn extract_symbols_from_tree_matches_list_symbols() {
4081 let path = fixture_path("sample.rs");
4082 let source = std::fs::read_to_string(&path).unwrap();
4083
4084 let provider = TreeSitterProvider::new();
4085 let listed = provider.list_symbols(&path).unwrap();
4086
4087 let mut parser = FileParser::new();
4088 let (tree, lang) = parser.parse(&path).unwrap();
4089 let extracted = extract_symbols_from_tree(&source, tree, lang).unwrap();
4090
4091 assert_eq!(symbols_as_debug(&extracted), symbols_as_debug(&listed));
4092 }
4093
4094 fn symbols_as_debug(symbols: &[Symbol]) -> Vec<String> {
4095 symbols
4096 .iter()
4097 .map(|symbol| {
4098 format!(
4099 "{}|{:?}|{}:{}-{}:{}|{:?}|{:?}|{}|{:?}",
4100 symbol.name,
4101 symbol.kind,
4102 symbol.range.start_line,
4103 symbol.range.start_col,
4104 symbol.range.end_line,
4105 symbol.range.end_col,
4106 symbol.signature,
4107 symbol.scope_chain,
4108 symbol.exported,
4109 symbol.parent,
4110 )
4111 })
4112 .collect()
4113 }
4114
4115 #[test]
4118 fn py_extracts_all_symbols() {
4119 let provider = TreeSitterProvider::new();
4120 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
4121
4122 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
4123 assert!(
4124 names.contains(&"top_level_function"),
4125 "missing top_level_function: {:?}",
4126 names
4127 );
4128 assert!(names.contains(&"MyClass"), "missing MyClass: {:?}", names);
4129 assert!(
4130 names.contains(&"instance_method"),
4131 "missing method instance_method: {:?}",
4132 names
4133 );
4134 assert!(
4135 names.contains(&"decorated_function"),
4136 "missing decorated_function: {:?}",
4137 names
4138 );
4139
4140 assert!(
4142 symbols.len() >= 4,
4143 "expected ≥4 symbols, got {}: {:?}",
4144 symbols.len(),
4145 names
4146 );
4147 }
4148
4149 #[test]
4150 fn py_symbol_kinds_correct() {
4151 let provider = TreeSitterProvider::new();
4152 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
4153
4154 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
4155
4156 assert_eq!(find("top_level_function").kind, SymbolKind::Function);
4157 assert_eq!(find("MyClass").kind, SymbolKind::Class);
4158 assert_eq!(find("instance_method").kind, SymbolKind::Method);
4159 assert_eq!(find("decorated_function").kind, SymbolKind::Function);
4160 assert_eq!(find("OuterClass").kind, SymbolKind::Class);
4161 assert_eq!(find("InnerClass").kind, SymbolKind::Class);
4162 assert_eq!(find("inner_method").kind, SymbolKind::Method);
4163 assert_eq!(find("outer_method").kind, SymbolKind::Method);
4164 }
4165
4166 #[test]
4167 fn py_method_scope_chain() {
4168 let provider = TreeSitterProvider::new();
4169 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
4170
4171 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
4172
4173 assert_eq!(
4175 find("instance_method").scope_chain,
4176 vec!["MyClass"],
4177 "instance_method should have MyClass in scope chain"
4178 );
4179 assert_eq!(find("instance_method").parent.as_deref(), Some("MyClass"));
4180
4181 assert_eq!(
4183 find("inner_method").scope_chain,
4184 vec!["OuterClass", "InnerClass"],
4185 "inner_method should have nested scope chain"
4186 );
4187
4188 assert_eq!(
4190 find("InnerClass").scope_chain,
4191 vec!["OuterClass"],
4192 "InnerClass should have OuterClass in scope"
4193 );
4194
4195 assert!(
4197 find("top_level_function").scope_chain.is_empty(),
4198 "top-level function should have empty scope chain"
4199 );
4200 }
4201
4202 #[test]
4203 fn py_decorated_function_signature() {
4204 let provider = TreeSitterProvider::new();
4205 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
4206
4207 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
4208
4209 let sig = find("decorated_function").signature.as_ref().unwrap();
4210 assert!(
4211 sig.contains("@staticmethod"),
4212 "decorated function signature should include decorator: {}",
4213 sig
4214 );
4215 assert!(
4216 sig.contains("def decorated_function"),
4217 "signature should include function def: {}",
4218 sig
4219 );
4220 }
4221
4222 #[test]
4223 fn py_ranges_valid() {
4224 let provider = TreeSitterProvider::new();
4225 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
4226
4227 for s in &symbols {
4228 assert!(
4229 s.range.end_line >= s.range.start_line,
4230 "symbol {} has invalid range: {:?}",
4231 s.name,
4232 s.range
4233 );
4234 }
4235 }
4236
4237 #[test]
4240 fn rs_extracts_all_symbols() {
4241 let provider = TreeSitterProvider::new();
4242 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
4243
4244 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
4245 assert!(
4246 names.contains(&"public_function"),
4247 "missing public_function: {:?}",
4248 names
4249 );
4250 assert!(
4251 names.contains(&"private_function"),
4252 "missing private_function: {:?}",
4253 names
4254 );
4255 assert!(names.contains(&"MyStruct"), "missing MyStruct: {:?}", names);
4256 assert!(names.contains(&"Color"), "missing enum Color: {:?}", names);
4257 assert!(
4258 names.contains(&"Drawable"),
4259 "missing trait Drawable: {:?}",
4260 names
4261 );
4262 assert!(
4264 names.contains(&"new"),
4265 "missing impl method new: {:?}",
4266 names
4267 );
4268
4269 assert!(
4271 symbols.len() >= 6,
4272 "expected ≥6 symbols, got {}: {:?}",
4273 symbols.len(),
4274 names
4275 );
4276 }
4277
4278 #[test]
4279 fn rs_symbol_kinds_correct() {
4280 let provider = TreeSitterProvider::new();
4281 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
4282
4283 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
4284
4285 assert_eq!(find("public_function").kind, SymbolKind::Function);
4286 assert_eq!(find("private_function").kind, SymbolKind::Function);
4287 assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
4288 assert_eq!(find("Color").kind, SymbolKind::Enum);
4289 assert_eq!(find("Drawable").kind, SymbolKind::Interface); assert_eq!(find("new").kind, SymbolKind::Method);
4291 }
4292
4293 #[test]
4294 fn rs_pub_export_detection() {
4295 let provider = TreeSitterProvider::new();
4296 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
4297
4298 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
4299
4300 assert!(
4301 find("public_function").exported,
4302 "pub fn should be exported"
4303 );
4304 assert!(
4305 !find("private_function").exported,
4306 "non-pub fn should not be exported"
4307 );
4308 assert!(find("MyStruct").exported, "pub struct should be exported");
4309 assert!(find("Color").exported, "pub enum should be exported");
4310 assert!(find("Drawable").exported, "pub trait should be exported");
4311 assert!(
4312 find("new").exported,
4313 "pub fn inside impl should be exported"
4314 );
4315 assert!(
4316 !find("helper").exported,
4317 "non-pub fn inside impl should not be exported"
4318 );
4319 }
4320
4321 #[test]
4322 fn rs_impl_method_scope_chain() {
4323 let provider = TreeSitterProvider::new();
4324 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
4325
4326 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
4327
4328 assert_eq!(
4330 find("new").scope_chain,
4331 vec!["MyStruct"],
4332 "impl method should have type in scope chain"
4333 );
4334 assert_eq!(find("new").parent.as_deref(), Some("MyStruct"));
4335
4336 assert!(
4338 find("public_function").scope_chain.is_empty(),
4339 "free function should have empty scope chain"
4340 );
4341 }
4342
4343 #[test]
4344 fn rs_trait_impl_scope_chain() {
4345 let provider = TreeSitterProvider::new();
4346 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
4347
4348 let draw = symbols.iter().find(|s| s.name == "draw").unwrap();
4350 assert_eq!(
4351 draw.scope_chain,
4352 vec!["Drawable for MyStruct"],
4353 "trait impl method should have 'Trait for Type' scope"
4354 );
4355 assert_eq!(draw.parent.as_deref(), Some("MyStruct"));
4356 }
4357
4358 #[test]
4359 fn rs_ranges_valid() {
4360 let provider = TreeSitterProvider::new();
4361 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
4362
4363 for s in &symbols {
4364 assert!(
4365 s.range.end_line >= s.range.start_line,
4366 "symbol {} has invalid range: {:?}",
4367 s.name,
4368 s.range
4369 );
4370 }
4371 }
4372
4373 #[test]
4376 fn go_extracts_all_symbols() {
4377 let provider = TreeSitterProvider::new();
4378 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
4379
4380 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
4381 assert!(
4382 names.contains(&"ExportedFunction"),
4383 "missing ExportedFunction: {:?}",
4384 names
4385 );
4386 assert!(
4387 names.contains(&"unexportedFunction"),
4388 "missing unexportedFunction: {:?}",
4389 names
4390 );
4391 assert!(
4392 names.contains(&"MyStruct"),
4393 "missing struct MyStruct: {:?}",
4394 names
4395 );
4396 assert!(
4397 names.contains(&"Reader"),
4398 "missing interface Reader: {:?}",
4399 names
4400 );
4401 assert!(
4403 names.contains(&"String"),
4404 "missing receiver method String: {:?}",
4405 names
4406 );
4407
4408 assert!(
4410 symbols.len() >= 4,
4411 "expected ≥4 symbols, got {}: {:?}",
4412 symbols.len(),
4413 names
4414 );
4415 }
4416
4417 #[test]
4418 fn go_symbol_kinds_correct() {
4419 let provider = TreeSitterProvider::new();
4420 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
4421
4422 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
4423
4424 assert_eq!(find("ExportedFunction").kind, SymbolKind::Function);
4425 assert_eq!(find("unexportedFunction").kind, SymbolKind::Function);
4426 assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
4427 assert_eq!(find("Reader").kind, SymbolKind::Interface);
4428 assert_eq!(find("String").kind, SymbolKind::Method);
4429 assert_eq!(find("helper").kind, SymbolKind::Method);
4430 }
4431
4432 #[test]
4433 fn go_uppercase_export_detection() {
4434 let provider = TreeSitterProvider::new();
4435 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
4436
4437 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
4438
4439 assert!(
4440 find("ExportedFunction").exported,
4441 "ExportedFunction (uppercase) should be exported"
4442 );
4443 assert!(
4444 !find("unexportedFunction").exported,
4445 "unexportedFunction (lowercase) should not be exported"
4446 );
4447 assert!(
4448 find("MyStruct").exported,
4449 "MyStruct (uppercase) should be exported"
4450 );
4451 assert!(
4452 find("Reader").exported,
4453 "Reader (uppercase) should be exported"
4454 );
4455 assert!(
4456 find("String").exported,
4457 "String method (uppercase) should be exported"
4458 );
4459 assert!(
4460 !find("helper").exported,
4461 "helper method (lowercase) should not be exported"
4462 );
4463 }
4464
4465 #[test]
4466 fn go_receiver_method_scope_chain() {
4467 let provider = TreeSitterProvider::new();
4468 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
4469
4470 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
4471
4472 assert_eq!(
4474 find("String").scope_chain,
4475 vec!["MyStruct"],
4476 "receiver method should have type in scope chain"
4477 );
4478 assert_eq!(find("String").parent.as_deref(), Some("MyStruct"));
4479
4480 assert!(
4482 find("ExportedFunction").scope_chain.is_empty(),
4483 "regular function should have empty scope chain"
4484 );
4485 }
4486
4487 #[test]
4488 fn go_ranges_valid() {
4489 let provider = TreeSitterProvider::new();
4490 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
4491
4492 for s in &symbols {
4493 assert!(
4494 s.range.end_line >= s.range.start_line,
4495 "symbol {} has invalid range: {:?}",
4496 s.name,
4497 s.range
4498 );
4499 }
4500 }
4501
4502 #[test]
4505 fn cross_language_all_six_produce_symbols() {
4506 let provider = TreeSitterProvider::new();
4507
4508 let fixtures = [
4509 ("sample.ts", "TypeScript"),
4510 ("sample.tsx", "TSX"),
4511 ("sample.js", "JavaScript"),
4512 ("sample.py", "Python"),
4513 ("sample.rs", "Rust"),
4514 ("sample.go", "Go"),
4515 ];
4516
4517 for (fixture, lang) in &fixtures {
4518 let symbols = provider
4519 .list_symbols(&fixture_path(fixture))
4520 .unwrap_or_else(|e| panic!("{} ({}) failed: {:?}", lang, fixture, e));
4521 assert!(
4522 symbols.len() >= 2,
4523 "{} should produce ≥2 symbols, got {}: {:?}",
4524 lang,
4525 symbols.len(),
4526 symbols.iter().map(|s| &s.name).collect::<Vec<_>>()
4527 );
4528 }
4529 }
4530
4531 #[test]
4534 fn symbol_cache_returns_cached_results_on_second_call() {
4535 let dir = tempfile::tempdir().unwrap();
4536 let file = dir.path().join("test.rs");
4537 std::fs::write(&file, "pub fn hello() {}\npub fn world() {}").unwrap();
4538
4539 let mut parser = FileParser::new();
4540
4541 let symbols1 = parser.extract_symbols(&file).unwrap();
4542 assert_eq!(symbols1.len(), 2);
4543
4544 let symbols2 = parser.extract_symbols(&file).unwrap();
4546 assert_eq!(symbols2.len(), 2);
4547 assert_eq!(symbols1[0].name, symbols2[0].name);
4548
4549 assert!(parser.symbol_cache.read().unwrap().contains_key(&file));
4551 }
4552
4553 #[test]
4554 fn symbol_cache_invalidates_on_file_change() {
4555 let dir = tempfile::tempdir().unwrap();
4556 let file = dir.path().join("test.rs");
4557 std::fs::write(&file, "pub fn hello() {}").unwrap();
4558
4559 let mut parser = FileParser::new();
4560
4561 let symbols1 = parser.extract_symbols(&file).unwrap();
4562 assert_eq!(symbols1.len(), 1);
4563 assert_eq!(symbols1[0].name, "hello");
4564
4565 std::thread::sleep(std::time::Duration::from_millis(50));
4567
4568 std::fs::write(&file, "pub fn hello() {}\npub fn goodbye() {}").unwrap();
4570
4571 let symbols2 = parser.extract_symbols(&file).unwrap();
4573 assert_eq!(symbols2.len(), 2);
4574 assert!(symbols2.iter().any(|s| s.name == "goodbye"));
4575 }
4576
4577 #[test]
4578 fn symbol_cache_invalidate_method_clears_entry() {
4579 let dir = tempfile::tempdir().unwrap();
4580 let file = dir.path().join("test.rs");
4581 std::fs::write(&file, "pub fn hello() {}").unwrap();
4582
4583 let mut parser = FileParser::new();
4584 parser.extract_symbols(&file).unwrap();
4585 assert!(parser.symbol_cache.read().unwrap().contains_key(&file));
4586
4587 parser.invalidate_symbols(&file);
4588 assert!(!parser.symbol_cache.read().unwrap().contains_key(&file));
4589 assert!(!parser.cache.contains_key(&file));
4591 }
4592
4593 #[test]
4594 fn symbol_cache_works_for_multiple_languages() {
4595 let dir = tempfile::tempdir().unwrap();
4596 let rs_file = dir.path().join("lib.rs");
4597 let ts_file = dir.path().join("app.ts");
4598 let py_file = dir.path().join("main.py");
4599
4600 std::fs::write(&rs_file, "pub fn rust_fn() {}").unwrap();
4601 std::fs::write(&ts_file, "export function tsFn() {}").unwrap();
4602 std::fs::write(&py_file, "def py_fn():\n pass").unwrap();
4603
4604 let mut parser = FileParser::new();
4605
4606 let rs_syms = parser.extract_symbols(&rs_file).unwrap();
4607 let ts_syms = parser.extract_symbols(&ts_file).unwrap();
4608 let py_syms = parser.extract_symbols(&py_file).unwrap();
4609
4610 assert!(rs_syms.iter().any(|s| s.name == "rust_fn"));
4611 assert!(ts_syms.iter().any(|s| s.name == "tsFn"));
4612 assert!(py_syms.iter().any(|s| s.name == "py_fn"));
4613
4614 assert_eq!(parser.symbol_cache.read().unwrap().len(), 3);
4616
4617 let rs_syms2 = parser.extract_symbols(&rs_file).unwrap();
4619 assert_eq!(rs_syms.len(), rs_syms2.len());
4620 }
4621}