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