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