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