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