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 total_lines = source.lines().count() as u32;
2591
2592 for i in 0..headings.len() {
2596 let level = headings[i].0;
2597 let section_end = headings[i + 1..]
2598 .iter()
2599 .find(|(l, _)| *l <= level)
2600 .map(|(_, s)| s.range.start_line.saturating_sub(1))
2601 .unwrap_or_else(|| total_lines.saturating_sub(1));
2602 headings[i].1.range.end_line = section_end;
2603 if section_end != headings[i].1.range.start_line {
2604 headings[i].1.range.end_col = 0;
2605 }
2606 }
2607
2608 let mut scope_stack: Vec<(u8, String)> = Vec::new(); for (level, symbol) in headings.iter_mut() {
2611 while scope_stack.last().is_some_and(|(l, _)| *l >= *level) {
2613 scope_stack.pop();
2614 }
2615 symbol.scope_chain = scope_stack.iter().map(|(_, name)| name.clone()).collect();
2616 symbol.parent = scope_stack.last().map(|(_, name)| name.clone());
2617 scope_stack.push((*level, symbol.name.clone()));
2618 }
2619
2620 Ok(headings.into_iter().map(|(_, s)| s).collect())
2621}
2622
2623fn collect_html_headings(source: &str, node: &Node, headings: &mut Vec<(u8, Symbol)>) {
2625 let mut cursor = node.walk();
2626 if !cursor.goto_first_child() {
2627 return;
2628 }
2629
2630 loop {
2631 let child = cursor.node();
2632 if child.kind() == "element" {
2633 if let Some(start_tag) = child
2635 .child_by_field_name("start_tag")
2636 .or_else(|| child.child(0).filter(|c| c.kind() == "start_tag"))
2637 {
2638 if let Some(tag_name_node) = start_tag
2639 .child_by_field_name("tag_name")
2640 .or_else(|| start_tag.child(1).filter(|c| c.kind() == "tag_name"))
2641 {
2642 let tag_name = node_text(source, &tag_name_node).to_lowercase();
2643 if let Some(level) = match tag_name.as_str() {
2644 "h1" => Some(1u8),
2645 "h2" => Some(2),
2646 "h3" => Some(3),
2647 "h4" => Some(4),
2648 "h5" => Some(5),
2649 "h6" => Some(6),
2650 _ => None,
2651 } {
2652 let text = extract_element_text(source, &child).trim().to_string();
2654 if !text.is_empty() {
2655 let range = node_range(&child);
2656 let signature = format!("<h{}> {}", level, text);
2657 headings.push((
2658 level,
2659 Symbol {
2660 name: text,
2661 kind: SymbolKind::Heading,
2662 range,
2663 signature: Some(signature),
2664 scope_chain: vec![], exported: false,
2666 parent: None, },
2668 ));
2669 }
2670 }
2671 }
2672 }
2673 collect_html_headings(source, &child, headings);
2675 } else {
2676 collect_html_headings(source, &child, headings);
2678 }
2679
2680 if !cursor.goto_next_sibling() {
2681 break;
2682 }
2683 }
2684}
2685
2686fn extract_element_text(source: &str, node: &Node) -> String {
2688 let mut text = String::new();
2689 let mut cursor = node.walk();
2690 if !cursor.goto_first_child() {
2691 return text;
2692 }
2693 loop {
2694 let child = cursor.node();
2695 match child.kind() {
2696 "text" => {
2697 text.push_str(node_text(source, &child));
2698 }
2699 "element" => {
2700 text.push_str(&extract_element_text(source, &child));
2702 }
2703 _ => {}
2704 }
2705 if !cursor.goto_next_sibling() {
2706 break;
2707 }
2708 }
2709 text
2710}
2711
2712fn extract_md_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
2716 let mut symbols = Vec::new();
2717 extract_md_sections(source, root, &mut symbols, &[]);
2718 Ok(symbols)
2719}
2720
2721fn extract_md_sections(
2723 source: &str,
2724 node: &Node,
2725 symbols: &mut Vec<Symbol>,
2726 scope_chain: &[String],
2727) {
2728 let mut cursor = node.walk();
2729 if !cursor.goto_first_child() {
2730 return;
2731 }
2732
2733 loop {
2734 let child = cursor.node();
2735 match child.kind() {
2736 "section" => {
2737 let mut section_cursor = child.walk();
2740 let mut heading_name = String::new();
2741 let mut heading_level: u8 = 0;
2742
2743 if section_cursor.goto_first_child() {
2744 loop {
2745 let section_child = section_cursor.node();
2746 if section_child.kind() == "atx_heading" {
2747 let mut h_cursor = section_child.walk();
2749 if h_cursor.goto_first_child() {
2750 loop {
2751 let h_child = h_cursor.node();
2752 let kind = h_child.kind();
2753 if kind.starts_with("atx_h") && kind.ends_with("_marker") {
2754 heading_level = kind
2756 .strip_prefix("atx_h")
2757 .and_then(|s| s.strip_suffix("_marker"))
2758 .and_then(|s| s.parse::<u8>().ok())
2759 .unwrap_or(1);
2760 } else if h_child.kind() == "inline" {
2761 heading_name =
2762 node_text(source, &h_child).trim().to_string();
2763 }
2764 if !h_cursor.goto_next_sibling() {
2765 break;
2766 }
2767 }
2768 }
2769 }
2770 if !section_cursor.goto_next_sibling() {
2771 break;
2772 }
2773 }
2774 }
2775
2776 if !heading_name.is_empty() {
2777 let range = node_range(&child);
2778 let signature = format!(
2779 "{} {}",
2780 "#".repeat((heading_level as usize).min(6)),
2781 heading_name
2782 );
2783
2784 symbols.push(Symbol {
2785 name: heading_name.clone(),
2786 kind: SymbolKind::Heading,
2787 range,
2788 signature: Some(signature),
2789 scope_chain: scope_chain.to_vec(),
2790 exported: false,
2791 parent: scope_chain.last().cloned(),
2792 });
2793
2794 let mut new_scope = scope_chain.to_vec();
2796 new_scope.push(heading_name);
2797 extract_md_sections(source, &child, symbols, &new_scope);
2798 }
2799 }
2800 _ => {}
2801 }
2802
2803 if !cursor.goto_next_sibling() {
2804 break;
2805 }
2806 }
2807}
2808
2809fn dedup_symbols(symbols: &mut Vec<Symbol>) {
2813 let mut seen = std::collections::HashSet::new();
2814 symbols.retain(|s| {
2815 let key = (s.name.clone(), format!("{:?}", s.kind), s.range.start_line);
2816 seen.insert(key)
2817 });
2818}
2819
2820pub struct TreeSitterProvider {
2823 parser: RefCell<FileParser>,
2824}
2825
2826#[derive(Debug, Clone)]
2827struct ReExportTarget {
2828 file: PathBuf,
2829 symbol_name: String,
2830}
2831
2832impl TreeSitterProvider {
2833 pub fn new() -> Self {
2835 Self {
2836 parser: RefCell::new(FileParser::new()),
2837 }
2838 }
2839
2840 pub fn merge_warm_cache(&self, cache: SymbolCache) {
2843 let mut parser = self.parser.borrow_mut();
2844 parser.set_warm_cache(cache);
2845 }
2846
2847 pub fn symbol_cache_stats(&self) -> (usize, usize) {
2849 let parser = self.parser.borrow();
2850 let local = parser.symbol_cache_len();
2851 let warm = parser.warm_cache_len();
2852 (local, warm)
2853 }
2854
2855 fn resolve_symbol_inner(
2856 &self,
2857 file: &Path,
2858 name: &str,
2859 depth: usize,
2860 visited: &mut HashSet<(PathBuf, String)>,
2861 ) -> Result<Vec<SymbolMatch>, AftError> {
2862 if depth > MAX_REEXPORT_DEPTH {
2863 return Ok(Vec::new());
2864 }
2865
2866 let canonical_file = std::fs::canonicalize(file).unwrap_or_else(|_| file.to_path_buf());
2867 if !visited.insert((canonical_file, name.to_string())) {
2868 return Ok(Vec::new());
2869 }
2870
2871 let symbols = self.parser.borrow_mut().extract_symbols(file)?;
2872 let local_matches = symbol_matches_in_file(file, &symbols, name);
2873 if !local_matches.is_empty() {
2874 return Ok(local_matches);
2875 }
2876
2877 if name == "default" {
2878 let default_matches = self.resolve_local_default_export(file, &symbols)?;
2879 if !default_matches.is_empty() {
2880 return Ok(default_matches);
2881 }
2882 }
2883
2884 let reexport_targets = self.collect_reexport_targets(file, name)?;
2885 let mut matches = Vec::new();
2886 let mut seen = HashSet::new();
2887 for target in reexport_targets {
2888 for resolved in
2889 self.resolve_symbol_inner(&target.file, &target.symbol_name, depth + 1, visited)?
2890 {
2891 let key = format!(
2892 "{}:{}:{}:{}:{}:{}",
2893 resolved.file,
2894 resolved.symbol.name,
2895 resolved.symbol.range.start_line,
2896 resolved.symbol.range.start_col,
2897 resolved.symbol.range.end_line,
2898 resolved.symbol.range.end_col
2899 );
2900 if seen.insert(key) {
2901 matches.push(resolved);
2902 }
2903 }
2904 }
2905
2906 Ok(matches)
2907 }
2908
2909 fn collect_reexport_targets(
2910 &self,
2911 file: &Path,
2912 requested_name: &str,
2913 ) -> Result<Vec<ReExportTarget>, AftError> {
2914 let (source, tree, lang) = self.read_parsed_file(file)?;
2915 if !matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
2916 return Ok(Vec::new());
2917 }
2918
2919 let mut targets = Vec::new();
2920 let root = tree.root_node();
2921 let from_dir = file.parent().unwrap_or_else(|| Path::new("."));
2922
2923 let mut cursor = root.walk();
2924 if !cursor.goto_first_child() {
2925 return Ok(targets);
2926 }
2927
2928 loop {
2929 let node = cursor.node();
2930 if node.kind() == "export_statement" {
2931 let Some(source_node) = node.child_by_field_name("source") else {
2932 if !cursor.goto_next_sibling() {
2933 break;
2934 }
2935 continue;
2936 };
2937
2938 let Some(module_path) = string_content(&source, &source_node) else {
2939 if !cursor.goto_next_sibling() {
2940 break;
2941 }
2942 continue;
2943 };
2944
2945 let Some(target_file) = resolve_module_path(from_dir, &module_path) else {
2946 if !cursor.goto_next_sibling() {
2947 break;
2948 }
2949 continue;
2950 };
2951
2952 if let Some(export_clause) = find_child_by_kind(node, "export_clause") {
2953 if let Some(symbol_name) =
2954 resolve_export_clause_name(&source, &export_clause, requested_name)
2955 {
2956 targets.push(ReExportTarget {
2957 file: target_file,
2958 symbol_name,
2959 });
2960 }
2961 } else if export_statement_has_wildcard(&source, &node) {
2962 targets.push(ReExportTarget {
2963 file: target_file,
2964 symbol_name: requested_name.to_string(),
2965 });
2966 }
2967 }
2968
2969 if !cursor.goto_next_sibling() {
2970 break;
2971 }
2972 }
2973
2974 Ok(targets)
2975 }
2976
2977 fn resolve_local_default_export(
2978 &self,
2979 file: &Path,
2980 symbols: &[Symbol],
2981 ) -> Result<Vec<SymbolMatch>, AftError> {
2982 let (source, tree, lang) = self.read_parsed_file(file)?;
2983 if !matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
2984 return Ok(Vec::new());
2985 }
2986
2987 let root = tree.root_node();
2988 let mut matches = Vec::new();
2989 let mut seen = HashSet::new();
2990
2991 let mut cursor = root.walk();
2992 if !cursor.goto_first_child() {
2993 return Ok(matches);
2994 }
2995
2996 loop {
2997 let node = cursor.node();
2998 if node.kind() == "export_statement"
2999 && node.child_by_field_name("source").is_none()
3000 && node_contains_token(&source, &node, "default")
3001 {
3002 if let Some(target_name) = default_export_target_name(&source, &node) {
3003 for symbol_match in symbol_matches_in_file(file, symbols, &target_name) {
3004 let key = format!(
3005 "{}:{}:{}:{}:{}:{}",
3006 symbol_match.file,
3007 symbol_match.symbol.name,
3008 symbol_match.symbol.range.start_line,
3009 symbol_match.symbol.range.start_col,
3010 symbol_match.symbol.range.end_line,
3011 symbol_match.symbol.range.end_col
3012 );
3013 if seen.insert(key) {
3014 matches.push(symbol_match);
3015 }
3016 }
3017 }
3018 }
3019
3020 if !cursor.goto_next_sibling() {
3021 break;
3022 }
3023 }
3024
3025 Ok(matches)
3026 }
3027
3028 fn read_parsed_file(&self, file: &Path) -> Result<(String, Tree, LangId), AftError> {
3029 let source = std::fs::read_to_string(file).map_err(|e| AftError::FileNotFound {
3030 path: format!("{}: {}", file.display(), e),
3031 })?;
3032 let (tree, lang) = {
3033 let mut parser = self.parser.borrow_mut();
3034 parser.parse_cloned(file)?
3035 };
3036 Ok((source, tree, lang))
3037 }
3038}
3039
3040fn symbol_matches_in_file(file: &Path, symbols: &[Symbol], name: &str) -> Vec<SymbolMatch> {
3041 symbols
3042 .iter()
3043 .filter(|symbol| symbol.name == name)
3044 .cloned()
3045 .map(|symbol| SymbolMatch {
3046 file: file.display().to_string(),
3047 symbol,
3048 })
3049 .collect()
3050}
3051
3052fn string_content(source: &str, node: &Node) -> Option<String> {
3053 let text = node_text(source, node);
3054 if text.len() < 2 {
3055 return None;
3056 }
3057
3058 Some(
3059 text.trim_start_matches(|c| c == '\'' || c == '"')
3060 .trim_end_matches(|c| c == '\'' || c == '"')
3061 .to_string(),
3062 )
3063}
3064
3065fn find_child_by_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
3066 let mut cursor = node.walk();
3067 if !cursor.goto_first_child() {
3068 return None;
3069 }
3070
3071 loop {
3072 let child = cursor.node();
3073 if child.kind() == kind {
3074 return Some(child);
3075 }
3076 if !cursor.goto_next_sibling() {
3077 break;
3078 }
3079 }
3080
3081 None
3082}
3083
3084fn resolve_export_clause_name(
3085 source: &str,
3086 export_clause: &Node,
3087 requested_name: &str,
3088) -> Option<String> {
3089 let mut cursor = export_clause.walk();
3090 if !cursor.goto_first_child() {
3091 return None;
3092 }
3093
3094 loop {
3095 let child = cursor.node();
3096 if child.kind() == "export_specifier" {
3097 let (source_name, exported_name) = export_specifier_names(source, &child)?;
3098 if exported_name == requested_name {
3099 return Some(source_name);
3100 }
3101 }
3102
3103 if !cursor.goto_next_sibling() {
3104 break;
3105 }
3106 }
3107
3108 None
3109}
3110
3111fn export_specifier_names(source: &str, specifier: &Node) -> Option<(String, String)> {
3112 let source_name = specifier
3113 .child_by_field_name("name")
3114 .map(|node| node_text(source, &node).to_string());
3115 let alias_name = specifier
3116 .child_by_field_name("alias")
3117 .map(|node| node_text(source, &node).to_string());
3118
3119 if let Some(source_name) = source_name {
3120 let exported_name = alias_name.unwrap_or_else(|| source_name.clone());
3121 return Some((source_name, exported_name));
3122 }
3123
3124 let mut names = Vec::new();
3125 let mut cursor = specifier.walk();
3126 if cursor.goto_first_child() {
3127 loop {
3128 let child = cursor.node();
3129 let child_text = node_text(source, &child).trim();
3130 if matches!(
3131 child.kind(),
3132 "identifier" | "type_identifier" | "property_identifier"
3133 ) || child_text == "default"
3134 {
3135 names.push(child_text.to_string());
3136 }
3137 if !cursor.goto_next_sibling() {
3138 break;
3139 }
3140 }
3141 }
3142
3143 match names.as_slice() {
3144 [name] => Some((name.clone(), name.clone())),
3145 [source_name, exported_name, ..] => Some((source_name.clone(), exported_name.clone())),
3146 _ => None,
3147 }
3148}
3149
3150fn export_statement_has_wildcard(source: &str, node: &Node) -> 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() == "*" {
3158 return true;
3159 }
3160 if !cursor.goto_next_sibling() {
3161 break;
3162 }
3163 }
3164
3165 false
3166}
3167
3168fn node_contains_token(source: &str, node: &Node, token: &str) -> bool {
3169 let mut cursor = node.walk();
3170 if !cursor.goto_first_child() {
3171 return false;
3172 }
3173
3174 loop {
3175 if node_text(source, &cursor.node()).trim() == token {
3176 return true;
3177 }
3178 if !cursor.goto_next_sibling() {
3179 break;
3180 }
3181 }
3182
3183 false
3184}
3185
3186fn default_export_target_name(source: &str, export_stmt: &Node) -> Option<String> {
3187 let mut cursor = export_stmt.walk();
3188 if !cursor.goto_first_child() {
3189 return None;
3190 }
3191
3192 loop {
3193 let child = cursor.node();
3194 match child.kind() {
3195 "function_declaration"
3196 | "class_declaration"
3197 | "interface_declaration"
3198 | "enum_declaration"
3199 | "type_alias_declaration"
3200 | "lexical_declaration" => {
3201 if let Some(name_node) = child.child_by_field_name("name") {
3202 return Some(node_text(source, &name_node).to_string());
3203 }
3204
3205 if child.kind() == "lexical_declaration" {
3206 let mut child_cursor = child.walk();
3207 if child_cursor.goto_first_child() {
3208 loop {
3209 let nested = child_cursor.node();
3210 if nested.kind() == "variable_declarator" {
3211 if let Some(name_node) = nested.child_by_field_name("name") {
3212 return Some(node_text(source, &name_node).to_string());
3213 }
3214 }
3215 if !child_cursor.goto_next_sibling() {
3216 break;
3217 }
3218 }
3219 }
3220 }
3221 }
3222 "identifier" | "type_identifier" => {
3223 let text = node_text(source, &child);
3224 if text != "export" && text != "default" {
3225 return Some(text.to_string());
3226 }
3227 }
3228 _ => {}
3229 }
3230
3231 if !cursor.goto_next_sibling() {
3232 break;
3233 }
3234 }
3235
3236 None
3237}
3238
3239impl crate::language::LanguageProvider for TreeSitterProvider {
3240 fn resolve_symbol(&self, file: &Path, name: &str) -> Result<Vec<SymbolMatch>, AftError> {
3241 let matches = self.resolve_symbol_inner(file, name, 0, &mut HashSet::new())?;
3242
3243 if matches.is_empty() {
3244 Err(AftError::SymbolNotFound {
3245 name: name.to_string(),
3246 file: file.display().to_string(),
3247 })
3248 } else {
3249 Ok(matches)
3250 }
3251 }
3252
3253 fn list_symbols(&self, file: &Path) -> Result<Vec<Symbol>, AftError> {
3254 self.parser.borrow_mut().extract_symbols(file)
3255 }
3256
3257 fn as_any(&self) -> &dyn std::any::Any {
3258 self
3259 }
3260}
3261
3262#[cfg(test)]
3263mod tests {
3264 use super::*;
3265 use crate::language::LanguageProvider;
3266 use std::path::PathBuf;
3267
3268 fn fixture_path(name: &str) -> PathBuf {
3269 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
3270 .join("tests")
3271 .join("fixtures")
3272 .join(name)
3273 }
3274
3275 #[test]
3278 fn detect_ts() {
3279 assert_eq!(
3280 detect_language(Path::new("foo.ts")),
3281 Some(LangId::TypeScript)
3282 );
3283 }
3284
3285 #[test]
3286 fn detect_tsx() {
3287 assert_eq!(detect_language(Path::new("foo.tsx")), Some(LangId::Tsx));
3288 }
3289
3290 #[test]
3291 fn detect_js() {
3292 assert_eq!(
3293 detect_language(Path::new("foo.js")),
3294 Some(LangId::JavaScript)
3295 );
3296 }
3297
3298 #[test]
3299 fn detect_jsx() {
3300 assert_eq!(
3301 detect_language(Path::new("foo.jsx")),
3302 Some(LangId::JavaScript)
3303 );
3304 }
3305
3306 #[test]
3307 fn detect_py() {
3308 assert_eq!(detect_language(Path::new("foo.py")), Some(LangId::Python));
3309 }
3310
3311 #[test]
3312 fn detect_rs() {
3313 assert_eq!(detect_language(Path::new("foo.rs")), Some(LangId::Rust));
3314 }
3315
3316 #[test]
3317 fn detect_go() {
3318 assert_eq!(detect_language(Path::new("foo.go")), Some(LangId::Go));
3319 }
3320
3321 #[test]
3322 fn detect_c() {
3323 assert_eq!(detect_language(Path::new("foo.c")), Some(LangId::C));
3324 }
3325
3326 #[test]
3327 fn detect_h() {
3328 assert_eq!(detect_language(Path::new("foo.h")), Some(LangId::C));
3329 }
3330
3331 #[test]
3332 fn detect_cc() {
3333 assert_eq!(detect_language(Path::new("foo.cc")), Some(LangId::Cpp));
3334 }
3335
3336 #[test]
3337 fn detect_cpp() {
3338 assert_eq!(detect_language(Path::new("foo.cpp")), Some(LangId::Cpp));
3339 }
3340
3341 #[test]
3342 fn detect_cxx() {
3343 assert_eq!(detect_language(Path::new("foo.cxx")), Some(LangId::Cpp));
3344 }
3345
3346 #[test]
3347 fn detect_hpp() {
3348 assert_eq!(detect_language(Path::new("foo.hpp")), Some(LangId::Cpp));
3349 }
3350
3351 #[test]
3352 fn detect_hh() {
3353 assert_eq!(detect_language(Path::new("foo.hh")), Some(LangId::Cpp));
3354 }
3355
3356 #[test]
3357 fn detect_zig() {
3358 assert_eq!(detect_language(Path::new("foo.zig")), Some(LangId::Zig));
3359 }
3360
3361 #[test]
3362 fn detect_cs() {
3363 assert_eq!(detect_language(Path::new("foo.cs")), Some(LangId::CSharp));
3364 }
3365
3366 #[test]
3367 fn detect_unknown_returns_none() {
3368 assert_eq!(detect_language(Path::new("foo.txt")), None);
3369 }
3370
3371 #[test]
3374 fn unsupported_extension_returns_invalid_request() {
3375 let path = fixture_path("sample.ts");
3377 let bad_path = path.with_extension("txt");
3378 std::fs::write(&bad_path, "hello").unwrap();
3380 let provider = TreeSitterProvider::new();
3381 let result = provider.list_symbols(&bad_path);
3382 std::fs::remove_file(&bad_path).ok();
3383 match result {
3384 Err(AftError::InvalidRequest { message }) => {
3385 assert!(
3386 message.contains("unsupported file extension"),
3387 "msg: {}",
3388 message
3389 );
3390 assert!(message.contains("txt"), "msg: {}", message);
3391 }
3392 other => panic!("expected InvalidRequest, got {:?}", other),
3393 }
3394 }
3395
3396 #[test]
3399 fn ts_extracts_all_symbol_kinds() {
3400 let provider = TreeSitterProvider::new();
3401 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
3402
3403 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
3404 assert!(
3405 names.contains(&"greet"),
3406 "missing function greet: {:?}",
3407 names
3408 );
3409 assert!(names.contains(&"add"), "missing arrow fn add: {:?}", names);
3410 assert!(
3411 names.contains(&"UserService"),
3412 "missing class UserService: {:?}",
3413 names
3414 );
3415 assert!(
3416 names.contains(&"Config"),
3417 "missing interface Config: {:?}",
3418 names
3419 );
3420 assert!(
3421 names.contains(&"Status"),
3422 "missing enum Status: {:?}",
3423 names
3424 );
3425 assert!(
3426 names.contains(&"UserId"),
3427 "missing type alias UserId: {:?}",
3428 names
3429 );
3430 assert!(
3431 names.contains(&"internalHelper"),
3432 "missing non-exported fn: {:?}",
3433 names
3434 );
3435
3436 assert!(
3438 symbols.len() >= 6,
3439 "expected ≥6 symbols, got {}: {:?}",
3440 symbols.len(),
3441 names
3442 );
3443 }
3444
3445 #[test]
3446 fn ts_symbol_kinds_correct() {
3447 let provider = TreeSitterProvider::new();
3448 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
3449
3450 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
3451
3452 assert_eq!(find("greet").kind, SymbolKind::Function);
3453 assert_eq!(find("add").kind, SymbolKind::Function); assert_eq!(find("UserService").kind, SymbolKind::Class);
3455 assert_eq!(find("Config").kind, SymbolKind::Interface);
3456 assert_eq!(find("Status").kind, SymbolKind::Enum);
3457 assert_eq!(find("UserId").kind, SymbolKind::TypeAlias);
3458 }
3459
3460 #[test]
3461 fn ts_export_detection() {
3462 let provider = TreeSitterProvider::new();
3463 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
3464
3465 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
3466
3467 assert!(find("greet").exported, "greet should be exported");
3468 assert!(find("add").exported, "add should be exported");
3469 assert!(
3470 find("UserService").exported,
3471 "UserService should be exported"
3472 );
3473 assert!(find("Config").exported, "Config should be exported");
3474 assert!(find("Status").exported, "Status should be exported");
3475 assert!(
3476 !find("internalHelper").exported,
3477 "internalHelper should not be exported"
3478 );
3479 }
3480
3481 #[test]
3482 fn ts_method_scope_chain() {
3483 let provider = TreeSitterProvider::new();
3484 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
3485
3486 let methods: Vec<&Symbol> = symbols
3487 .iter()
3488 .filter(|s| s.kind == SymbolKind::Method)
3489 .collect();
3490 assert!(!methods.is_empty(), "should have at least one method");
3491
3492 for method in &methods {
3493 assert_eq!(
3494 method.scope_chain,
3495 vec!["UserService"],
3496 "method {} should have UserService in scope chain",
3497 method.name
3498 );
3499 assert_eq!(method.parent.as_deref(), Some("UserService"));
3500 }
3501 }
3502
3503 #[test]
3504 fn ts_signatures_present() {
3505 let provider = TreeSitterProvider::new();
3506 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
3507
3508 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
3509
3510 let greet_sig = find("greet").signature.as_ref().unwrap();
3511 assert!(
3512 greet_sig.contains("greet"),
3513 "signature should contain function name: {}",
3514 greet_sig
3515 );
3516 }
3517
3518 #[test]
3519 fn ts_ranges_valid() {
3520 let provider = TreeSitterProvider::new();
3521 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
3522
3523 for s in &symbols {
3524 assert!(
3525 s.range.end_line >= s.range.start_line,
3526 "symbol {} has invalid range: {:?}",
3527 s.name,
3528 s.range
3529 );
3530 }
3531 }
3532
3533 #[test]
3536 fn js_extracts_core_symbols() {
3537 let provider = TreeSitterProvider::new();
3538 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
3539
3540 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
3541 assert!(
3542 names.contains(&"multiply"),
3543 "missing function multiply: {:?}",
3544 names
3545 );
3546 assert!(
3547 names.contains(&"divide"),
3548 "missing arrow fn divide: {:?}",
3549 names
3550 );
3551 assert!(
3552 names.contains(&"EventEmitter"),
3553 "missing class EventEmitter: {:?}",
3554 names
3555 );
3556 assert!(
3557 names.contains(&"main"),
3558 "missing default export fn main: {:?}",
3559 names
3560 );
3561
3562 assert!(
3563 symbols.len() >= 4,
3564 "expected ≥4 symbols, got {}: {:?}",
3565 symbols.len(),
3566 names
3567 );
3568 }
3569
3570 #[test]
3571 fn js_arrow_fn_correctly_named() {
3572 let provider = TreeSitterProvider::new();
3573 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
3574
3575 let divide = symbols.iter().find(|s| s.name == "divide").unwrap();
3576 assert_eq!(divide.kind, SymbolKind::Function);
3577 assert!(divide.exported, "divide should be exported");
3578
3579 let internal = symbols.iter().find(|s| s.name == "internalUtil").unwrap();
3580 assert_eq!(internal.kind, SymbolKind::Function);
3581 assert!(!internal.exported, "internalUtil should not be exported");
3582 }
3583
3584 #[test]
3585 fn js_method_scope_chain() {
3586 let provider = TreeSitterProvider::new();
3587 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
3588
3589 let methods: Vec<&Symbol> = symbols
3590 .iter()
3591 .filter(|s| s.kind == SymbolKind::Method)
3592 .collect();
3593
3594 for method in &methods {
3595 assert_eq!(
3596 method.scope_chain,
3597 vec!["EventEmitter"],
3598 "method {} should have EventEmitter in scope chain",
3599 method.name
3600 );
3601 }
3602 }
3603
3604 #[test]
3607 fn tsx_extracts_react_component() {
3608 let provider = TreeSitterProvider::new();
3609 let symbols = provider.list_symbols(&fixture_path("sample.tsx")).unwrap();
3610
3611 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
3612 assert!(
3613 names.contains(&"Button"),
3614 "missing React component Button: {:?}",
3615 names
3616 );
3617 assert!(
3618 names.contains(&"Counter"),
3619 "missing class Counter: {:?}",
3620 names
3621 );
3622 assert!(
3623 names.contains(&"formatLabel"),
3624 "missing function formatLabel: {:?}",
3625 names
3626 );
3627
3628 assert!(
3629 symbols.len() >= 2,
3630 "expected ≥2 symbols, got {}: {:?}",
3631 symbols.len(),
3632 names
3633 );
3634 }
3635
3636 #[test]
3637 fn tsx_jsx_doesnt_break_parser() {
3638 let provider = TreeSitterProvider::new();
3640 let result = provider.list_symbols(&fixture_path("sample.tsx"));
3641 assert!(
3642 result.is_ok(),
3643 "TSX parsing should succeed: {:?}",
3644 result.err()
3645 );
3646 }
3647
3648 #[test]
3651 fn resolve_symbol_finds_match() {
3652 let provider = TreeSitterProvider::new();
3653 let matches = provider
3654 .resolve_symbol(&fixture_path("sample.ts"), "greet")
3655 .unwrap();
3656 assert_eq!(matches.len(), 1);
3657 assert_eq!(matches[0].symbol.name, "greet");
3658 assert_eq!(matches[0].symbol.kind, SymbolKind::Function);
3659 }
3660
3661 #[test]
3662 fn resolve_symbol_not_found() {
3663 let provider = TreeSitterProvider::new();
3664 let result = provider.resolve_symbol(&fixture_path("sample.ts"), "nonexistent");
3665 assert!(matches!(result, Err(AftError::SymbolNotFound { .. })));
3666 }
3667
3668 #[test]
3669 fn resolve_symbol_follows_reexport_chains() {
3670 let dir = tempfile::tempdir().unwrap();
3671 let config = dir.path().join("config.ts");
3672 let barrel1 = dir.path().join("barrel1.ts");
3673 let barrel2 = dir.path().join("barrel2.ts");
3674 let barrel3 = dir.path().join("barrel3.ts");
3675 let index = dir.path().join("index.ts");
3676
3677 std::fs::write(
3678 &config,
3679 "export class Config {}\nexport default class DefaultConfig {}\n",
3680 )
3681 .unwrap();
3682 std::fs::write(
3683 &barrel1,
3684 "export { Config } from './config';\nexport { default as NamedDefault } from './config';\n",
3685 )
3686 .unwrap();
3687 std::fs::write(
3688 &barrel2,
3689 "export { Config as RenamedConfig } from './barrel1';\n",
3690 )
3691 .unwrap();
3692 std::fs::write(
3693 &barrel3,
3694 "export * from './barrel2';\nexport * from './barrel1';\n",
3695 )
3696 .unwrap();
3697 std::fs::write(
3698 &index,
3699 "export class Config {}\nexport { RenamedConfig as FinalConfig } from './barrel3';\nexport * from './barrel3';\n",
3700 )
3701 .unwrap();
3702
3703 let provider = TreeSitterProvider::new();
3704 let config_canon = std::fs::canonicalize(&config).unwrap();
3705
3706 let direct = provider.resolve_symbol(&barrel1, "Config").unwrap();
3707 assert_eq!(direct.len(), 1);
3708 assert_eq!(direct[0].symbol.name, "Config");
3709 assert_eq!(direct[0].file, config_canon.display().to_string());
3710
3711 let renamed = provider.resolve_symbol(&barrel2, "RenamedConfig").unwrap();
3712 assert_eq!(renamed.len(), 1);
3713 assert_eq!(renamed[0].symbol.name, "Config");
3714 assert_eq!(renamed[0].file, config_canon.display().to_string());
3715
3716 let wildcard_chain = provider.resolve_symbol(&index, "FinalConfig").unwrap();
3717 assert_eq!(wildcard_chain.len(), 1);
3718 assert_eq!(wildcard_chain[0].symbol.name, "Config");
3719 assert_eq!(wildcard_chain[0].file, config_canon.display().to_string());
3720
3721 let wildcard_default = provider.resolve_symbol(&index, "NamedDefault").unwrap();
3722 assert_eq!(wildcard_default.len(), 1);
3723 assert_eq!(wildcard_default[0].symbol.name, "DefaultConfig");
3724 assert_eq!(wildcard_default[0].file, config_canon.display().to_string());
3725
3726 let local = provider.resolve_symbol(&index, "Config").unwrap();
3727 assert_eq!(local.len(), 1);
3728 assert_eq!(local[0].symbol.name, "Config");
3729 assert_eq!(local[0].file, index.display().to_string());
3730 }
3731
3732 #[test]
3735 fn symbol_range_includes_rust_attributes() {
3736 let dir = tempfile::tempdir().unwrap();
3737 let path = dir.path().join("test_attrs.rs");
3738 std::fs::write(
3739 &path,
3740 "/// This is a doc comment\n#[test]\n#[cfg(test)]\nfn my_test_fn() {\n assert!(true);\n}\n",
3741 )
3742 .unwrap();
3743
3744 let provider = TreeSitterProvider::new();
3745 let matches = provider.resolve_symbol(&path, "my_test_fn").unwrap();
3746 assert_eq!(matches.len(), 1);
3747 assert_eq!(
3748 matches[0].symbol.range.start_line, 0,
3749 "symbol range should include preceding /// doc comment, got start_line={}",
3750 matches[0].symbol.range.start_line
3751 );
3752 }
3753
3754 #[test]
3755 fn symbol_range_includes_go_doc_comment() {
3756 let dir = tempfile::tempdir().unwrap();
3757 let path = dir.path().join("test_doc.go");
3758 std::fs::write(
3759 &path,
3760 "package main\n\n// MyFunc does something useful.\n// It has a multi-line doc.\nfunc MyFunc() {\n}\n",
3761 )
3762 .unwrap();
3763
3764 let provider = TreeSitterProvider::new();
3765 let matches = provider.resolve_symbol(&path, "MyFunc").unwrap();
3766 assert_eq!(matches.len(), 1);
3767 assert_eq!(
3768 matches[0].symbol.range.start_line, 2,
3769 "symbol range should include preceding doc comments, got start_line={}",
3770 matches[0].symbol.range.start_line
3771 );
3772 }
3773
3774 #[test]
3775 fn symbol_range_skips_unrelated_comments() {
3776 let dir = tempfile::tempdir().unwrap();
3777 let path = dir.path().join("test_gap.go");
3778 std::fs::write(
3779 &path,
3780 "package main\n\n// This is a standalone comment\n\nfunc Standalone() {\n}\n",
3781 )
3782 .unwrap();
3783
3784 let provider = TreeSitterProvider::new();
3785 let matches = provider.resolve_symbol(&path, "Standalone").unwrap();
3786 assert_eq!(matches.len(), 1);
3787 assert_eq!(
3788 matches[0].symbol.range.start_line, 4,
3789 "symbol range should NOT include comment separated by blank line, got start_line={}",
3790 matches[0].symbol.range.start_line
3791 );
3792 }
3793
3794 #[test]
3795 fn parse_cache_returns_same_tree() {
3796 let mut parser = FileParser::new();
3797 let path = fixture_path("sample.ts");
3798
3799 let (tree1, _) = parser.parse(&path).unwrap();
3800 let tree1_root = tree1.root_node().byte_range();
3801
3802 let (tree2, _) = parser.parse(&path).unwrap();
3803 let tree2_root = tree2.root_node().byte_range();
3804
3805 assert_eq!(tree1_root, tree2_root);
3807 }
3808
3809 #[test]
3810 fn extract_symbols_from_tree_matches_list_symbols() {
3811 let path = fixture_path("sample.rs");
3812 let source = std::fs::read_to_string(&path).unwrap();
3813
3814 let provider = TreeSitterProvider::new();
3815 let listed = provider.list_symbols(&path).unwrap();
3816
3817 let mut parser = FileParser::new();
3818 let (tree, lang) = parser.parse(&path).unwrap();
3819 let extracted = extract_symbols_from_tree(&source, tree, lang).unwrap();
3820
3821 assert_eq!(symbols_as_debug(&extracted), symbols_as_debug(&listed));
3822 }
3823
3824 fn symbols_as_debug(symbols: &[Symbol]) -> Vec<String> {
3825 symbols
3826 .iter()
3827 .map(|symbol| {
3828 format!(
3829 "{}|{:?}|{}:{}-{}:{}|{:?}|{:?}|{}|{:?}",
3830 symbol.name,
3831 symbol.kind,
3832 symbol.range.start_line,
3833 symbol.range.start_col,
3834 symbol.range.end_line,
3835 symbol.range.end_col,
3836 symbol.signature,
3837 symbol.scope_chain,
3838 symbol.exported,
3839 symbol.parent,
3840 )
3841 })
3842 .collect()
3843 }
3844
3845 #[test]
3848 fn py_extracts_all_symbols() {
3849 let provider = TreeSitterProvider::new();
3850 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
3851
3852 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
3853 assert!(
3854 names.contains(&"top_level_function"),
3855 "missing top_level_function: {:?}",
3856 names
3857 );
3858 assert!(names.contains(&"MyClass"), "missing MyClass: {:?}", names);
3859 assert!(
3860 names.contains(&"instance_method"),
3861 "missing method instance_method: {:?}",
3862 names
3863 );
3864 assert!(
3865 names.contains(&"decorated_function"),
3866 "missing decorated_function: {:?}",
3867 names
3868 );
3869
3870 assert!(
3872 symbols.len() >= 4,
3873 "expected ≥4 symbols, got {}: {:?}",
3874 symbols.len(),
3875 names
3876 );
3877 }
3878
3879 #[test]
3880 fn py_symbol_kinds_correct() {
3881 let provider = TreeSitterProvider::new();
3882 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
3883
3884 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
3885
3886 assert_eq!(find("top_level_function").kind, SymbolKind::Function);
3887 assert_eq!(find("MyClass").kind, SymbolKind::Class);
3888 assert_eq!(find("instance_method").kind, SymbolKind::Method);
3889 assert_eq!(find("decorated_function").kind, SymbolKind::Function);
3890 assert_eq!(find("OuterClass").kind, SymbolKind::Class);
3891 assert_eq!(find("InnerClass").kind, SymbolKind::Class);
3892 assert_eq!(find("inner_method").kind, SymbolKind::Method);
3893 assert_eq!(find("outer_method").kind, SymbolKind::Method);
3894 }
3895
3896 #[test]
3897 fn py_method_scope_chain() {
3898 let provider = TreeSitterProvider::new();
3899 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
3900
3901 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
3902
3903 assert_eq!(
3905 find("instance_method").scope_chain,
3906 vec!["MyClass"],
3907 "instance_method should have MyClass in scope chain"
3908 );
3909 assert_eq!(find("instance_method").parent.as_deref(), Some("MyClass"));
3910
3911 assert_eq!(
3913 find("inner_method").scope_chain,
3914 vec!["OuterClass", "InnerClass"],
3915 "inner_method should have nested scope chain"
3916 );
3917
3918 assert_eq!(
3920 find("InnerClass").scope_chain,
3921 vec!["OuterClass"],
3922 "InnerClass should have OuterClass in scope"
3923 );
3924
3925 assert!(
3927 find("top_level_function").scope_chain.is_empty(),
3928 "top-level function should have empty scope chain"
3929 );
3930 }
3931
3932 #[test]
3933 fn py_decorated_function_signature() {
3934 let provider = TreeSitterProvider::new();
3935 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
3936
3937 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
3938
3939 let sig = find("decorated_function").signature.as_ref().unwrap();
3940 assert!(
3941 sig.contains("@staticmethod"),
3942 "decorated function signature should include decorator: {}",
3943 sig
3944 );
3945 assert!(
3946 sig.contains("def decorated_function"),
3947 "signature should include function def: {}",
3948 sig
3949 );
3950 }
3951
3952 #[test]
3953 fn py_ranges_valid() {
3954 let provider = TreeSitterProvider::new();
3955 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
3956
3957 for s in &symbols {
3958 assert!(
3959 s.range.end_line >= s.range.start_line,
3960 "symbol {} has invalid range: {:?}",
3961 s.name,
3962 s.range
3963 );
3964 }
3965 }
3966
3967 #[test]
3970 fn rs_extracts_all_symbols() {
3971 let provider = TreeSitterProvider::new();
3972 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
3973
3974 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
3975 assert!(
3976 names.contains(&"public_function"),
3977 "missing public_function: {:?}",
3978 names
3979 );
3980 assert!(
3981 names.contains(&"private_function"),
3982 "missing private_function: {:?}",
3983 names
3984 );
3985 assert!(names.contains(&"MyStruct"), "missing MyStruct: {:?}", names);
3986 assert!(names.contains(&"Color"), "missing enum Color: {:?}", names);
3987 assert!(
3988 names.contains(&"Drawable"),
3989 "missing trait Drawable: {:?}",
3990 names
3991 );
3992 assert!(
3994 names.contains(&"new"),
3995 "missing impl method new: {:?}",
3996 names
3997 );
3998
3999 assert!(
4001 symbols.len() >= 6,
4002 "expected ≥6 symbols, got {}: {:?}",
4003 symbols.len(),
4004 names
4005 );
4006 }
4007
4008 #[test]
4009 fn rs_symbol_kinds_correct() {
4010 let provider = TreeSitterProvider::new();
4011 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
4012
4013 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
4014
4015 assert_eq!(find("public_function").kind, SymbolKind::Function);
4016 assert_eq!(find("private_function").kind, SymbolKind::Function);
4017 assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
4018 assert_eq!(find("Color").kind, SymbolKind::Enum);
4019 assert_eq!(find("Drawable").kind, SymbolKind::Interface); assert_eq!(find("new").kind, SymbolKind::Method);
4021 }
4022
4023 #[test]
4024 fn rs_pub_export_detection() {
4025 let provider = TreeSitterProvider::new();
4026 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
4027
4028 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
4029
4030 assert!(
4031 find("public_function").exported,
4032 "pub fn should be exported"
4033 );
4034 assert!(
4035 !find("private_function").exported,
4036 "non-pub fn should not be exported"
4037 );
4038 assert!(find("MyStruct").exported, "pub struct should be exported");
4039 assert!(find("Color").exported, "pub enum should be exported");
4040 assert!(find("Drawable").exported, "pub trait should be exported");
4041 assert!(
4042 find("new").exported,
4043 "pub fn inside impl should be exported"
4044 );
4045 assert!(
4046 !find("helper").exported,
4047 "non-pub fn inside impl should not be exported"
4048 );
4049 }
4050
4051 #[test]
4052 fn rs_impl_method_scope_chain() {
4053 let provider = TreeSitterProvider::new();
4054 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
4055
4056 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
4057
4058 assert_eq!(
4060 find("new").scope_chain,
4061 vec!["MyStruct"],
4062 "impl method should have type in scope chain"
4063 );
4064 assert_eq!(find("new").parent.as_deref(), Some("MyStruct"));
4065
4066 assert!(
4068 find("public_function").scope_chain.is_empty(),
4069 "free function should have empty scope chain"
4070 );
4071 }
4072
4073 #[test]
4074 fn rs_trait_impl_scope_chain() {
4075 let provider = TreeSitterProvider::new();
4076 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
4077
4078 let draw = symbols.iter().find(|s| s.name == "draw").unwrap();
4080 assert_eq!(
4081 draw.scope_chain,
4082 vec!["Drawable for MyStruct"],
4083 "trait impl method should have 'Trait for Type' scope"
4084 );
4085 assert_eq!(draw.parent.as_deref(), Some("MyStruct"));
4086 }
4087
4088 #[test]
4089 fn rs_ranges_valid() {
4090 let provider = TreeSitterProvider::new();
4091 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
4092
4093 for s in &symbols {
4094 assert!(
4095 s.range.end_line >= s.range.start_line,
4096 "symbol {} has invalid range: {:?}",
4097 s.name,
4098 s.range
4099 );
4100 }
4101 }
4102
4103 #[test]
4106 fn go_extracts_all_symbols() {
4107 let provider = TreeSitterProvider::new();
4108 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
4109
4110 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
4111 assert!(
4112 names.contains(&"ExportedFunction"),
4113 "missing ExportedFunction: {:?}",
4114 names
4115 );
4116 assert!(
4117 names.contains(&"unexportedFunction"),
4118 "missing unexportedFunction: {:?}",
4119 names
4120 );
4121 assert!(
4122 names.contains(&"MyStruct"),
4123 "missing struct MyStruct: {:?}",
4124 names
4125 );
4126 assert!(
4127 names.contains(&"Reader"),
4128 "missing interface Reader: {:?}",
4129 names
4130 );
4131 assert!(
4133 names.contains(&"String"),
4134 "missing receiver method String: {:?}",
4135 names
4136 );
4137
4138 assert!(
4140 symbols.len() >= 4,
4141 "expected ≥4 symbols, got {}: {:?}",
4142 symbols.len(),
4143 names
4144 );
4145 }
4146
4147 #[test]
4148 fn go_symbol_kinds_correct() {
4149 let provider = TreeSitterProvider::new();
4150 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
4151
4152 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
4153
4154 assert_eq!(find("ExportedFunction").kind, SymbolKind::Function);
4155 assert_eq!(find("unexportedFunction").kind, SymbolKind::Function);
4156 assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
4157 assert_eq!(find("Reader").kind, SymbolKind::Interface);
4158 assert_eq!(find("String").kind, SymbolKind::Method);
4159 assert_eq!(find("helper").kind, SymbolKind::Method);
4160 }
4161
4162 #[test]
4163 fn go_uppercase_export_detection() {
4164 let provider = TreeSitterProvider::new();
4165 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
4166
4167 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
4168
4169 assert!(
4170 find("ExportedFunction").exported,
4171 "ExportedFunction (uppercase) should be exported"
4172 );
4173 assert!(
4174 !find("unexportedFunction").exported,
4175 "unexportedFunction (lowercase) should not be exported"
4176 );
4177 assert!(
4178 find("MyStruct").exported,
4179 "MyStruct (uppercase) should be exported"
4180 );
4181 assert!(
4182 find("Reader").exported,
4183 "Reader (uppercase) should be exported"
4184 );
4185 assert!(
4186 find("String").exported,
4187 "String method (uppercase) should be exported"
4188 );
4189 assert!(
4190 !find("helper").exported,
4191 "helper method (lowercase) should not be exported"
4192 );
4193 }
4194
4195 #[test]
4196 fn go_receiver_method_scope_chain() {
4197 let provider = TreeSitterProvider::new();
4198 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
4199
4200 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
4201
4202 assert_eq!(
4204 find("String").scope_chain,
4205 vec!["MyStruct"],
4206 "receiver method should have type in scope chain"
4207 );
4208 assert_eq!(find("String").parent.as_deref(), Some("MyStruct"));
4209
4210 assert!(
4212 find("ExportedFunction").scope_chain.is_empty(),
4213 "regular function should have empty scope chain"
4214 );
4215 }
4216
4217 #[test]
4218 fn go_ranges_valid() {
4219 let provider = TreeSitterProvider::new();
4220 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
4221
4222 for s in &symbols {
4223 assert!(
4224 s.range.end_line >= s.range.start_line,
4225 "symbol {} has invalid range: {:?}",
4226 s.name,
4227 s.range
4228 );
4229 }
4230 }
4231
4232 #[test]
4235 fn cross_language_all_six_produce_symbols() {
4236 let provider = TreeSitterProvider::new();
4237
4238 let fixtures = [
4239 ("sample.ts", "TypeScript"),
4240 ("sample.tsx", "TSX"),
4241 ("sample.js", "JavaScript"),
4242 ("sample.py", "Python"),
4243 ("sample.rs", "Rust"),
4244 ("sample.go", "Go"),
4245 ];
4246
4247 for (fixture, lang) in &fixtures {
4248 let symbols = provider
4249 .list_symbols(&fixture_path(fixture))
4250 .unwrap_or_else(|e| panic!("{} ({}) failed: {:?}", lang, fixture, e));
4251 assert!(
4252 symbols.len() >= 2,
4253 "{} should produce ≥2 symbols, got {}: {:?}",
4254 lang,
4255 symbols.len(),
4256 symbols.iter().map(|s| &s.name).collect::<Vec<_>>()
4257 );
4258 }
4259 }
4260
4261 #[test]
4264 fn symbol_cache_returns_cached_results_on_second_call() {
4265 let dir = tempfile::tempdir().unwrap();
4266 let file = dir.path().join("test.rs");
4267 std::fs::write(&file, "pub fn hello() {}\npub fn world() {}").unwrap();
4268
4269 let mut parser = FileParser::new();
4270
4271 let symbols1 = parser.extract_symbols(&file).unwrap();
4272 assert_eq!(symbols1.len(), 2);
4273
4274 let symbols2 = parser.extract_symbols(&file).unwrap();
4276 assert_eq!(symbols2.len(), 2);
4277 assert_eq!(symbols1[0].name, symbols2[0].name);
4278
4279 assert!(parser.symbol_cache.contains_key(&file));
4281 }
4282
4283 #[test]
4284 fn symbol_cache_invalidates_on_file_change() {
4285 let dir = tempfile::tempdir().unwrap();
4286 let file = dir.path().join("test.rs");
4287 std::fs::write(&file, "pub fn hello() {}").unwrap();
4288
4289 let mut parser = FileParser::new();
4290
4291 let symbols1 = parser.extract_symbols(&file).unwrap();
4292 assert_eq!(symbols1.len(), 1);
4293 assert_eq!(symbols1[0].name, "hello");
4294
4295 std::thread::sleep(std::time::Duration::from_millis(50));
4297
4298 std::fs::write(&file, "pub fn hello() {}\npub fn goodbye() {}").unwrap();
4300
4301 let symbols2 = parser.extract_symbols(&file).unwrap();
4303 assert_eq!(symbols2.len(), 2);
4304 assert!(symbols2.iter().any(|s| s.name == "goodbye"));
4305 }
4306
4307 #[test]
4308 fn symbol_cache_invalidate_method_clears_entry() {
4309 let dir = tempfile::tempdir().unwrap();
4310 let file = dir.path().join("test.rs");
4311 std::fs::write(&file, "pub fn hello() {}").unwrap();
4312
4313 let mut parser = FileParser::new();
4314 parser.extract_symbols(&file).unwrap();
4315 assert!(parser.symbol_cache.contains_key(&file));
4316
4317 parser.invalidate_symbols(&file);
4318 assert!(!parser.symbol_cache.contains_key(&file));
4319 assert!(!parser.cache.contains_key(&file));
4321 }
4322
4323 #[test]
4324 fn symbol_cache_works_for_multiple_languages() {
4325 let dir = tempfile::tempdir().unwrap();
4326 let rs_file = dir.path().join("lib.rs");
4327 let ts_file = dir.path().join("app.ts");
4328 let py_file = dir.path().join("main.py");
4329
4330 std::fs::write(&rs_file, "pub fn rust_fn() {}").unwrap();
4331 std::fs::write(&ts_file, "export function tsFn() {}").unwrap();
4332 std::fs::write(&py_file, "def py_fn():\n pass").unwrap();
4333
4334 let mut parser = FileParser::new();
4335
4336 let rs_syms = parser.extract_symbols(&rs_file).unwrap();
4337 let ts_syms = parser.extract_symbols(&ts_file).unwrap();
4338 let py_syms = parser.extract_symbols(&py_file).unwrap();
4339
4340 assert!(rs_syms.iter().any(|s| s.name == "rust_fn"));
4341 assert!(ts_syms.iter().any(|s| s.name == "tsFn"));
4342 assert!(py_syms.iter().any(|s| s.name == "py_fn"));
4343
4344 assert_eq!(parser.symbol_cache.len(), 3);
4346
4347 let rs_syms2 = parser.extract_symbols(&rs_file).unwrap();
4349 assert_eq!(rs_syms.len(), rs_syms2.len());
4350 }
4351}