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