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