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