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