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