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