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