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
145#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
147pub enum LangId {
148 TypeScript,
149 Tsx,
150 JavaScript,
151 Python,
152 Rust,
153 Go,
154 Markdown,
155}
156
157pub fn detect_language(path: &Path) -> Option<LangId> {
159 let ext = path.extension()?.to_str()?;
160 match ext {
161 "ts" => Some(LangId::TypeScript),
162 "tsx" => Some(LangId::Tsx),
163 "js" | "jsx" => Some(LangId::JavaScript),
164 "py" => Some(LangId::Python),
165 "rs" => Some(LangId::Rust),
166 "go" => Some(LangId::Go),
167 "md" | "markdown" | "mdx" => Some(LangId::Markdown),
168 _ => None,
169 }
170}
171
172pub fn grammar_for(lang: LangId) -> Language {
174 match lang {
175 LangId::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
176 LangId::Tsx => tree_sitter_typescript::LANGUAGE_TSX.into(),
177 LangId::JavaScript => tree_sitter_javascript::LANGUAGE.into(),
178 LangId::Python => tree_sitter_python::LANGUAGE.into(),
179 LangId::Rust => tree_sitter_rust::LANGUAGE.into(),
180 LangId::Go => tree_sitter_go::LANGUAGE.into(),
181 LangId::Markdown => tree_sitter_md::LANGUAGE.into(),
182 }
183}
184
185fn query_for(lang: LangId) -> Option<&'static str> {
187 match lang {
188 LangId::TypeScript | LangId::Tsx => Some(TS_QUERY),
189 LangId::JavaScript => Some(JS_QUERY),
190 LangId::Python => Some(PY_QUERY),
191 LangId::Rust => Some(RS_QUERY),
192 LangId::Go => Some(GO_QUERY),
193 LangId::Markdown => None,
194 }
195}
196
197struct CachedTree {
199 mtime: SystemTime,
200 tree: Tree,
201}
202
203pub struct FileParser {
206 cache: HashMap<PathBuf, CachedTree>,
207}
208
209impl FileParser {
210 pub fn new() -> Self {
212 Self {
213 cache: HashMap::new(),
214 }
215 }
216
217 pub fn parse(&mut self, path: &Path) -> Result<(&Tree, LangId), AftError> {
220 let lang = detect_language(path).ok_or_else(|| AftError::InvalidRequest {
221 message: format!(
222 "unsupported file extension: {}",
223 path.extension()
224 .and_then(|e| e.to_str())
225 .unwrap_or("<none>")
226 ),
227 })?;
228
229 let canon = path.to_path_buf();
230 let current_mtime = std::fs::metadata(path)
231 .and_then(|m| m.modified())
232 .map_err(|e| AftError::FileNotFound {
233 path: format!("{}: {}", path.display(), e),
234 })?;
235
236 let needs_reparse = match self.cache.get(&canon) {
238 Some(cached) => cached.mtime != current_mtime,
239 None => true,
240 };
241
242 if needs_reparse {
243 let source = std::fs::read_to_string(path).map_err(|e| AftError::FileNotFound {
244 path: format!("{}: {}", path.display(), e),
245 })?;
246
247 let grammar = grammar_for(lang);
248 let mut parser = Parser::new();
249 parser.set_language(&grammar).map_err(|e| {
250 log::error!("grammar init failed for {:?}: {}", lang, e);
251 AftError::ParseError {
252 message: format!("grammar init failed for {:?}: {}", lang, e),
253 }
254 })?;
255
256 let tree = parser.parse(&source, None).ok_or_else(|| {
257 log::error!("parse failed for {}", path.display());
258 AftError::ParseError {
259 message: format!("tree-sitter parse returned None for {}", path.display()),
260 }
261 })?;
262
263 self.cache.insert(
264 canon.clone(),
265 CachedTree {
266 mtime: current_mtime,
267 tree,
268 },
269 );
270 }
271
272 let cached = self.cache.get(&canon).ok_or_else(|| AftError::ParseError {
273 message: format!("parser cache missing entry for {}", path.display()),
274 })?;
275 Ok((&cached.tree, lang))
276 }
277
278 pub fn parse_cloned(&mut self, path: &Path) -> Result<(Tree, LangId), AftError> {
283 let (tree, lang) = self.parse(path)?;
284 Ok((tree.clone(), lang))
285 }
286
287 pub fn extract_symbols(&mut self, path: &Path) -> Result<Vec<Symbol>, AftError> {
289 let source = std::fs::read_to_string(path).map_err(|e| AftError::FileNotFound {
290 path: format!("{}: {}", path.display(), e),
291 })?;
292
293 let (tree, lang) = self.parse(path)?;
294 let root = tree.root_node();
295
296 if lang == LangId::Markdown {
298 return extract_md_symbols(&source, &root);
299 }
300
301 let query_src = query_for(lang).ok_or_else(|| AftError::InvalidRequest {
302 message: format!("no query patterns implemented for {:?} yet", lang),
303 })?;
304
305 let grammar = grammar_for(lang);
306 let query = Query::new(&grammar, query_src).map_err(|e| {
307 log::error!("query compile failed for {:?}: {}", lang, e);
308 AftError::ParseError {
309 message: format!("query compile error for {:?}: {}", lang, e),
310 }
311 })?;
312
313 match lang {
314 LangId::TypeScript | LangId::Tsx => extract_ts_symbols(&source, &root, &query),
315 LangId::JavaScript => extract_js_symbols(&source, &root, &query),
316 LangId::Python => extract_py_symbols(&source, &root, &query),
317 LangId::Rust => extract_rs_symbols(&source, &root, &query),
318 LangId::Go => extract_go_symbols(&source, &root, &query),
319 LangId::Markdown => unreachable!(),
320 }
321 }
322}
323
324pub(crate) fn node_range(node: &Node) -> Range {
326 let start = node.start_position();
327 let end = node.end_position();
328 Range {
329 start_line: start.row as u32,
330 start_col: start.column as u32,
331 end_line: end.row as u32,
332 end_col: end.column as u32,
333 }
334}
335
336pub(crate) fn node_range_with_decorators(node: &Node, source: &str, lang: LangId) -> Range {
342 let mut range = node_range(node);
343
344 let mut current = *node;
345 while let Some(prev) = current.prev_sibling() {
346 let kind = prev.kind();
347 let should_include = match lang {
348 LangId::Rust => {
349 kind == "attribute_item"
351 || (kind == "line_comment"
353 && node_text(source, &prev).starts_with("///"))
354 || (kind == "block_comment"
356 && node_text(source, &prev).starts_with("/**"))
357 }
358 LangId::TypeScript | LangId::Tsx | LangId::JavaScript => {
359 kind == "decorator"
361 || (kind == "comment"
363 && node_text(source, &prev).starts_with("/**"))
364 }
365 LangId::Go => {
366 kind == "comment" && is_adjacent_line(&prev, ¤t, source)
368 }
369 LangId::Python => {
370 false
372 }
373 LangId::Markdown => false,
374 };
375
376 if should_include {
377 range.start_line = prev.start_position().row as u32;
378 range.start_col = prev.start_position().column as u32;
379 current = prev;
380 } else {
381 break;
382 }
383 }
384
385 range
386}
387
388fn is_adjacent_line(upper: &Node, lower: &Node, source: &str) -> bool {
390 let upper_end = upper.end_position().row;
391 let lower_start = lower.start_position().row;
392
393 if lower_start == 0 || lower_start <= upper_end {
394 return true;
395 }
396
397 let lines: Vec<&str> = source.lines().collect();
399 for row in (upper_end + 1)..lower_start {
400 if row < lines.len() && lines[row].trim().is_empty() {
401 return false;
402 }
403 }
404 true
405}
406
407pub(crate) fn node_text<'a>(source: &'a str, node: &Node) -> &'a str {
409 &source[node.byte_range()]
410}
411
412fn lexical_declaration_has_function_value(node: &Node) -> bool {
413 let mut cursor = node.walk();
414 if !cursor.goto_first_child() {
415 return false;
416 }
417
418 loop {
419 let child = cursor.node();
420 if matches!(
421 child.kind(),
422 "arrow_function" | "function_expression" | "generator_function"
423 ) {
424 return true;
425 }
426
427 if lexical_declaration_has_function_value(&child) {
428 return true;
429 }
430
431 if !cursor.goto_next_sibling() {
432 break;
433 }
434 }
435
436 false
437}
438
439fn collect_export_ranges(source: &str, root: &Node, query: &Query) -> Vec<std::ops::Range<usize>> {
441 let export_idx = query
442 .capture_names()
443 .iter()
444 .position(|n| *n == "export.stmt");
445 let export_idx = match export_idx {
446 Some(i) => i as u32,
447 None => return vec![],
448 };
449
450 let mut cursor = QueryCursor::new();
451 let mut ranges = Vec::new();
452 let mut matches = cursor.matches(query, *root, source.as_bytes());
453
454 while let Some(m) = {
455 matches.advance();
456 matches.get()
457 } {
458 for cap in m.captures {
459 if cap.index == export_idx {
460 ranges.push(cap.node.byte_range());
461 }
462 }
463 }
464 ranges
465}
466
467fn is_exported(node: &Node, export_ranges: &[std::ops::Range<usize>]) -> bool {
469 let r = node.byte_range();
470 export_ranges
471 .iter()
472 .any(|er| er.start <= r.start && r.end <= er.end)
473}
474
475fn extract_signature(source: &str, node: &Node) -> String {
477 let text = node_text(source, node);
478 let first_line = text.lines().next().unwrap_or(text);
479 let trimmed = first_line.trim_end();
481 let trimmed = trimmed.strip_suffix('{').unwrap_or(trimmed).trim_end();
482 trimmed.to_string()
483}
484
485fn extract_ts_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
487 let lang = LangId::TypeScript;
488 let capture_names = query.capture_names();
489
490 let export_ranges = collect_export_ranges(source, root, query);
491
492 let mut symbols = Vec::new();
493 let mut cursor = QueryCursor::new();
494 let mut matches = cursor.matches(query, *root, source.as_bytes());
495
496 while let Some(m) = {
497 matches.advance();
498 matches.get()
499 } {
500 let mut fn_name_node = None;
502 let mut fn_def_node = None;
503 let mut arrow_name_node = None;
504 let mut arrow_def_node = None;
505 let mut class_name_node = None;
506 let mut class_def_node = None;
507 let mut method_class_name_node = None;
508 let mut method_name_node = None;
509 let mut method_def_node = None;
510 let mut interface_name_node = None;
511 let mut interface_def_node = None;
512 let mut enum_name_node = None;
513 let mut enum_def_node = None;
514 let mut type_alias_name_node = None;
515 let mut type_alias_def_node = None;
516 let mut var_name_node = None;
517 let mut var_def_node = None;
518
519 for cap in m.captures {
520 let name = capture_names[cap.index as usize];
521 match name {
522 "fn.name" => fn_name_node = Some(cap.node),
523 "fn.def" => fn_def_node = Some(cap.node),
524 "arrow.name" => arrow_name_node = Some(cap.node),
525 "arrow.def" => arrow_def_node = Some(cap.node),
526 "class.name" => class_name_node = Some(cap.node),
527 "class.def" => class_def_node = Some(cap.node),
528 "method.class_name" => method_class_name_node = Some(cap.node),
529 "method.name" => method_name_node = Some(cap.node),
530 "method.def" => method_def_node = Some(cap.node),
531 "interface.name" => interface_name_node = Some(cap.node),
532 "interface.def" => interface_def_node = Some(cap.node),
533 "enum.name" => enum_name_node = Some(cap.node),
534 "enum.def" => enum_def_node = Some(cap.node),
535 "type_alias.name" => type_alias_name_node = Some(cap.node),
536 "type_alias.def" => type_alias_def_node = Some(cap.node),
537 "var.name" => var_name_node = Some(cap.node),
538 "var.def" => var_def_node = Some(cap.node),
539 _ => {}
541 }
542 }
543
544 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
546 symbols.push(Symbol {
547 name: node_text(source, &name_node).to_string(),
548 kind: SymbolKind::Function,
549 range: node_range_with_decorators(&def_node, source, lang),
550 signature: Some(extract_signature(source, &def_node)),
551 scope_chain: vec![],
552 exported: is_exported(&def_node, &export_ranges),
553 parent: None,
554 });
555 }
556
557 if let (Some(name_node), Some(def_node)) = (arrow_name_node, arrow_def_node) {
559 symbols.push(Symbol {
560 name: node_text(source, &name_node).to_string(),
561 kind: SymbolKind::Function,
562 range: node_range_with_decorators(&def_node, source, lang),
563 signature: Some(extract_signature(source, &def_node)),
564 scope_chain: vec![],
565 exported: is_exported(&def_node, &export_ranges),
566 parent: None,
567 });
568 }
569
570 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
572 symbols.push(Symbol {
573 name: node_text(source, &name_node).to_string(),
574 kind: SymbolKind::Class,
575 range: node_range_with_decorators(&def_node, source, lang),
576 signature: Some(extract_signature(source, &def_node)),
577 scope_chain: vec![],
578 exported: is_exported(&def_node, &export_ranges),
579 parent: None,
580 });
581 }
582
583 if let (Some(class_name_node), Some(name_node), Some(def_node)) =
585 (method_class_name_node, method_name_node, method_def_node)
586 {
587 let class_name = node_text(source, &class_name_node).to_string();
588 symbols.push(Symbol {
589 name: node_text(source, &name_node).to_string(),
590 kind: SymbolKind::Method,
591 range: node_range_with_decorators(&def_node, source, lang),
592 signature: Some(extract_signature(source, &def_node)),
593 scope_chain: vec![class_name.clone()],
594 exported: false, parent: Some(class_name),
596 });
597 }
598
599 if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
601 symbols.push(Symbol {
602 name: node_text(source, &name_node).to_string(),
603 kind: SymbolKind::Interface,
604 range: node_range_with_decorators(&def_node, source, lang),
605 signature: Some(extract_signature(source, &def_node)),
606 scope_chain: vec![],
607 exported: is_exported(&def_node, &export_ranges),
608 parent: None,
609 });
610 }
611
612 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
614 symbols.push(Symbol {
615 name: node_text(source, &name_node).to_string(),
616 kind: SymbolKind::Enum,
617 range: node_range_with_decorators(&def_node, source, lang),
618 signature: Some(extract_signature(source, &def_node)),
619 scope_chain: vec![],
620 exported: is_exported(&def_node, &export_ranges),
621 parent: None,
622 });
623 }
624
625 if let (Some(name_node), Some(def_node)) = (type_alias_name_node, type_alias_def_node) {
627 symbols.push(Symbol {
628 name: node_text(source, &name_node).to_string(),
629 kind: SymbolKind::TypeAlias,
630 range: node_range_with_decorators(&def_node, source, lang),
631 signature: Some(extract_signature(source, &def_node)),
632 scope_chain: vec![],
633 exported: is_exported(&def_node, &export_ranges),
634 parent: None,
635 });
636 }
637
638 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
640 let is_top_level = def_node
642 .parent()
643 .map(|p| p.kind() == "program" || p.kind() == "export_statement")
644 .unwrap_or(false);
645 let is_function_like = lexical_declaration_has_function_value(&def_node);
646 let name = node_text(source, &name_node).to_string();
647 let already_captured = symbols.iter().any(|s| s.name == name);
648 if is_top_level && !is_function_like && !already_captured {
649 symbols.push(Symbol {
650 name,
651 kind: SymbolKind::Variable,
652 range: node_range_with_decorators(&def_node, source, lang),
653 signature: Some(extract_signature(source, &def_node)),
654 scope_chain: vec![],
655 exported: is_exported(&def_node, &export_ranges),
656 parent: None,
657 });
658 }
659 }
660 }
661
662 dedup_symbols(&mut symbols);
664 Ok(symbols)
665}
666
667fn extract_js_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
669 let lang = LangId::JavaScript;
670 let capture_names = query.capture_names();
671
672 let export_ranges = collect_export_ranges(source, root, query);
673
674 let mut symbols = Vec::new();
675 let mut cursor = QueryCursor::new();
676 let mut matches = cursor.matches(query, *root, source.as_bytes());
677
678 while let Some(m) = {
679 matches.advance();
680 matches.get()
681 } {
682 let mut fn_name_node = None;
683 let mut fn_def_node = None;
684 let mut arrow_name_node = None;
685 let mut arrow_def_node = None;
686 let mut class_name_node = None;
687 let mut class_def_node = None;
688 let mut method_class_name_node = None;
689 let mut method_name_node = None;
690 let mut method_def_node = None;
691
692 for cap in m.captures {
693 let name = capture_names[cap.index as usize];
694 match name {
695 "fn.name" => fn_name_node = Some(cap.node),
696 "fn.def" => fn_def_node = Some(cap.node),
697 "arrow.name" => arrow_name_node = Some(cap.node),
698 "arrow.def" => arrow_def_node = Some(cap.node),
699 "class.name" => class_name_node = Some(cap.node),
700 "class.def" => class_def_node = Some(cap.node),
701 "method.class_name" => method_class_name_node = Some(cap.node),
702 "method.name" => method_name_node = Some(cap.node),
703 "method.def" => method_def_node = Some(cap.node),
704 _ => {}
705 }
706 }
707
708 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
709 symbols.push(Symbol {
710 name: node_text(source, &name_node).to_string(),
711 kind: SymbolKind::Function,
712 range: node_range_with_decorators(&def_node, source, lang),
713 signature: Some(extract_signature(source, &def_node)),
714 scope_chain: vec![],
715 exported: is_exported(&def_node, &export_ranges),
716 parent: None,
717 });
718 }
719
720 if let (Some(name_node), Some(def_node)) = (arrow_name_node, arrow_def_node) {
721 symbols.push(Symbol {
722 name: node_text(source, &name_node).to_string(),
723 kind: SymbolKind::Function,
724 range: node_range_with_decorators(&def_node, source, lang),
725 signature: Some(extract_signature(source, &def_node)),
726 scope_chain: vec![],
727 exported: is_exported(&def_node, &export_ranges),
728 parent: None,
729 });
730 }
731
732 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
733 symbols.push(Symbol {
734 name: node_text(source, &name_node).to_string(),
735 kind: SymbolKind::Class,
736 range: node_range_with_decorators(&def_node, source, lang),
737 signature: Some(extract_signature(source, &def_node)),
738 scope_chain: vec![],
739 exported: is_exported(&def_node, &export_ranges),
740 parent: None,
741 });
742 }
743
744 if let (Some(class_name_node), Some(name_node), Some(def_node)) =
745 (method_class_name_node, method_name_node, method_def_node)
746 {
747 let class_name = node_text(source, &class_name_node).to_string();
748 symbols.push(Symbol {
749 name: node_text(source, &name_node).to_string(),
750 kind: SymbolKind::Method,
751 range: node_range_with_decorators(&def_node, source, lang),
752 signature: Some(extract_signature(source, &def_node)),
753 scope_chain: vec![class_name.clone()],
754 exported: false,
755 parent: Some(class_name),
756 });
757 }
758 }
759
760 dedup_symbols(&mut symbols);
761 Ok(symbols)
762}
763
764fn py_scope_chain(node: &Node, source: &str) -> Vec<String> {
767 let mut chain = Vec::new();
768 let mut current = node.parent();
769 while let Some(parent) = current {
770 if parent.kind() == "class_definition" {
771 if let Some(name_node) = parent.child_by_field_name("name") {
772 chain.push(node_text(source, &name_node).to_string());
773 }
774 }
775 current = parent.parent();
776 }
777 chain.reverse();
778 chain
779}
780
781fn extract_py_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
783 let lang = LangId::Python;
784 let capture_names = query.capture_names();
785
786 let mut symbols = Vec::new();
787 let mut cursor = QueryCursor::new();
788 let mut matches = cursor.matches(query, *root, source.as_bytes());
789
790 let mut decorated_fn_lines = std::collections::HashSet::new();
792
793 {
795 let mut cursor2 = QueryCursor::new();
796 let mut matches2 = cursor2.matches(query, *root, source.as_bytes());
797 while let Some(m) = {
798 matches2.advance();
799 matches2.get()
800 } {
801 let mut dec_def_node = None;
802 let mut dec_decorator_node = None;
803
804 for cap in m.captures {
805 let name = capture_names[cap.index as usize];
806 match name {
807 "dec.def" => dec_def_node = Some(cap.node),
808 "dec.decorator" => dec_decorator_node = Some(cap.node),
809 _ => {}
810 }
811 }
812
813 if let (Some(def_node), Some(_dec_node)) = (dec_def_node, dec_decorator_node) {
814 let mut child_cursor = def_node.walk();
816 if child_cursor.goto_first_child() {
817 loop {
818 let child = child_cursor.node();
819 if child.kind() == "function_definition"
820 || child.kind() == "class_definition"
821 {
822 decorated_fn_lines.insert(child.start_position().row);
823 }
824 if !child_cursor.goto_next_sibling() {
825 break;
826 }
827 }
828 }
829 }
830 }
831 }
832
833 while let Some(m) = {
834 matches.advance();
835 matches.get()
836 } {
837 let mut fn_name_node = None;
838 let mut fn_def_node = None;
839 let mut class_name_node = None;
840 let mut class_def_node = None;
841
842 for cap in m.captures {
843 let name = capture_names[cap.index as usize];
844 match name {
845 "fn.name" => fn_name_node = Some(cap.node),
846 "fn.def" => fn_def_node = Some(cap.node),
847 "class.name" => class_name_node = Some(cap.node),
848 "class.def" => class_def_node = Some(cap.node),
849 _ => {}
850 }
851 }
852
853 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
855 let scope = py_scope_chain(&def_node, source);
856 let is_method = !scope.is_empty();
857 let name = node_text(source, &name_node).to_string();
858 let kind = if is_method {
860 SymbolKind::Method
861 } else {
862 SymbolKind::Function
863 };
864
865 let sig = if decorated_fn_lines.contains(&def_node.start_position().row) {
867 let mut sig_parts = Vec::new();
869 let mut parent = def_node.parent();
870 while let Some(p) = parent {
871 if p.kind() == "decorated_definition" {
872 let mut dc = p.walk();
874 if dc.goto_first_child() {
875 loop {
876 if dc.node().kind() == "decorator" {
877 sig_parts.push(node_text(source, &dc.node()).to_string());
878 }
879 if !dc.goto_next_sibling() {
880 break;
881 }
882 }
883 }
884 break;
885 }
886 parent = p.parent();
887 }
888 sig_parts.push(extract_signature(source, &def_node));
889 Some(sig_parts.join("\n"))
890 } else {
891 Some(extract_signature(source, &def_node))
892 };
893
894 symbols.push(Symbol {
895 name,
896 kind,
897 range: node_range_with_decorators(&def_node, source, lang),
898 signature: sig,
899 scope_chain: scope.clone(),
900 exported: false, parent: scope.last().cloned(),
902 });
903 }
904
905 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
907 let scope = py_scope_chain(&def_node, source);
908
909 let sig = if decorated_fn_lines.contains(&def_node.start_position().row) {
911 let mut sig_parts = Vec::new();
912 let mut parent = def_node.parent();
913 while let Some(p) = parent {
914 if p.kind() == "decorated_definition" {
915 let mut dc = p.walk();
916 if dc.goto_first_child() {
917 loop {
918 if dc.node().kind() == "decorator" {
919 sig_parts.push(node_text(source, &dc.node()).to_string());
920 }
921 if !dc.goto_next_sibling() {
922 break;
923 }
924 }
925 }
926 break;
927 }
928 parent = p.parent();
929 }
930 sig_parts.push(extract_signature(source, &def_node));
931 Some(sig_parts.join("\n"))
932 } else {
933 Some(extract_signature(source, &def_node))
934 };
935
936 symbols.push(Symbol {
937 name: node_text(source, &name_node).to_string(),
938 kind: SymbolKind::Class,
939 range: node_range_with_decorators(&def_node, source, lang),
940 signature: sig,
941 scope_chain: scope.clone(),
942 exported: false,
943 parent: scope.last().cloned(),
944 });
945 }
946 }
947
948 dedup_symbols(&mut symbols);
949 Ok(symbols)
950}
951
952fn extract_rs_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
955 let lang = LangId::Rust;
956 let capture_names = query.capture_names();
957
958 let mut vis_ranges: Vec<std::ops::Range<usize>> = Vec::new();
960 {
961 let vis_idx = capture_names.iter().position(|n| *n == "vis.mod");
962 if let Some(idx) = vis_idx {
963 let idx = idx as u32;
964 let mut cursor = QueryCursor::new();
965 let mut matches = cursor.matches(query, *root, source.as_bytes());
966 while let Some(m) = {
967 matches.advance();
968 matches.get()
969 } {
970 for cap in m.captures {
971 if cap.index == idx {
972 vis_ranges.push(cap.node.byte_range());
973 }
974 }
975 }
976 }
977 }
978
979 let is_pub = |node: &Node| -> bool {
980 let mut child_cursor = node.walk();
982 if child_cursor.goto_first_child() {
983 loop {
984 if child_cursor.node().kind() == "visibility_modifier" {
985 return true;
986 }
987 if !child_cursor.goto_next_sibling() {
988 break;
989 }
990 }
991 }
992 false
993 };
994
995 let mut symbols = Vec::new();
996 let mut cursor = QueryCursor::new();
997 let mut matches = cursor.matches(query, *root, source.as_bytes());
998
999 while let Some(m) = {
1000 matches.advance();
1001 matches.get()
1002 } {
1003 let mut fn_name_node = None;
1004 let mut fn_def_node = None;
1005 let mut struct_name_node = None;
1006 let mut struct_def_node = None;
1007 let mut enum_name_node = None;
1008 let mut enum_def_node = None;
1009 let mut trait_name_node = None;
1010 let mut trait_def_node = None;
1011 let mut impl_def_node = None;
1012
1013 for cap in m.captures {
1014 let name = capture_names[cap.index as usize];
1015 match name {
1016 "fn.name" => fn_name_node = Some(cap.node),
1017 "fn.def" => fn_def_node = Some(cap.node),
1018 "struct.name" => struct_name_node = Some(cap.node),
1019 "struct.def" => struct_def_node = Some(cap.node),
1020 "enum.name" => enum_name_node = Some(cap.node),
1021 "enum.def" => enum_def_node = Some(cap.node),
1022 "trait.name" => trait_name_node = Some(cap.node),
1023 "trait.def" => trait_def_node = Some(cap.node),
1024 "impl.def" => impl_def_node = Some(cap.node),
1025 _ => {}
1026 }
1027 }
1028
1029 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1031 let parent = def_node.parent();
1032 let in_impl = parent
1033 .map(|p| p.kind() == "declaration_list")
1034 .unwrap_or(false);
1035 if !in_impl {
1036 symbols.push(Symbol {
1037 name: node_text(source, &name_node).to_string(),
1038 kind: SymbolKind::Function,
1039 range: node_range_with_decorators(&def_node, source, lang),
1040 signature: Some(extract_signature(source, &def_node)),
1041 scope_chain: vec![],
1042 exported: is_pub(&def_node),
1043 parent: None,
1044 });
1045 }
1046 }
1047
1048 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
1050 symbols.push(Symbol {
1051 name: node_text(source, &name_node).to_string(),
1052 kind: SymbolKind::Struct,
1053 range: node_range_with_decorators(&def_node, source, lang),
1054 signature: Some(extract_signature(source, &def_node)),
1055 scope_chain: vec![],
1056 exported: is_pub(&def_node),
1057 parent: None,
1058 });
1059 }
1060
1061 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
1063 symbols.push(Symbol {
1064 name: node_text(source, &name_node).to_string(),
1065 kind: SymbolKind::Enum,
1066 range: node_range_with_decorators(&def_node, source, lang),
1067 signature: Some(extract_signature(source, &def_node)),
1068 scope_chain: vec![],
1069 exported: is_pub(&def_node),
1070 parent: None,
1071 });
1072 }
1073
1074 if let (Some(name_node), Some(def_node)) = (trait_name_node, trait_def_node) {
1076 symbols.push(Symbol {
1077 name: node_text(source, &name_node).to_string(),
1078 kind: SymbolKind::Interface,
1079 range: node_range_with_decorators(&def_node, source, lang),
1080 signature: Some(extract_signature(source, &def_node)),
1081 scope_chain: vec![],
1082 exported: is_pub(&def_node),
1083 parent: None,
1084 });
1085 }
1086
1087 if let Some(impl_node) = impl_def_node {
1089 let mut type_names: Vec<String> = Vec::new();
1093 let mut child_cursor = impl_node.walk();
1094 if child_cursor.goto_first_child() {
1095 loop {
1096 let child = child_cursor.node();
1097 if child.kind() == "type_identifier" || child.kind() == "generic_type" {
1098 type_names.push(node_text(source, &child).to_string());
1099 }
1100 if !child_cursor.goto_next_sibling() {
1101 break;
1102 }
1103 }
1104 }
1105
1106 let scope_name = if type_names.len() >= 2 {
1107 format!("{} for {}", type_names[0], type_names[1])
1109 } else if type_names.len() == 1 {
1110 type_names[0].clone()
1111 } else {
1112 String::new()
1113 };
1114
1115 let parent_name = type_names.last().cloned().unwrap_or_default();
1116
1117 let mut child_cursor = impl_node.walk();
1119 if child_cursor.goto_first_child() {
1120 loop {
1121 let child = child_cursor.node();
1122 if child.kind() == "declaration_list" {
1123 let mut fn_cursor = child.walk();
1124 if fn_cursor.goto_first_child() {
1125 loop {
1126 let fn_node = fn_cursor.node();
1127 if fn_node.kind() == "function_item" {
1128 if let Some(name_node) = fn_node.child_by_field_name("name") {
1129 symbols.push(Symbol {
1130 name: node_text(source, &name_node).to_string(),
1131 kind: SymbolKind::Method,
1132 range: node_range_with_decorators(
1133 &fn_node, source, lang,
1134 ),
1135 signature: Some(extract_signature(source, &fn_node)),
1136 scope_chain: if scope_name.is_empty() {
1137 vec![]
1138 } else {
1139 vec![scope_name.clone()]
1140 },
1141 exported: is_pub(&fn_node),
1142 parent: if parent_name.is_empty() {
1143 None
1144 } else {
1145 Some(parent_name.clone())
1146 },
1147 });
1148 }
1149 }
1150 if !fn_cursor.goto_next_sibling() {
1151 break;
1152 }
1153 }
1154 }
1155 }
1156 if !child_cursor.goto_next_sibling() {
1157 break;
1158 }
1159 }
1160 }
1161 }
1162 }
1163
1164 dedup_symbols(&mut symbols);
1165 Ok(symbols)
1166}
1167
1168fn extract_go_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1172 let lang = LangId::Go;
1173 let capture_names = query.capture_names();
1174
1175 let is_go_exported = |name: &str| -> bool {
1176 name.chars()
1177 .next()
1178 .map(|c| c.is_uppercase())
1179 .unwrap_or(false)
1180 };
1181
1182 let mut symbols = Vec::new();
1183 let mut cursor = QueryCursor::new();
1184 let mut matches = cursor.matches(query, *root, source.as_bytes());
1185
1186 while let Some(m) = {
1187 matches.advance();
1188 matches.get()
1189 } {
1190 let mut fn_name_node = None;
1191 let mut fn_def_node = None;
1192 let mut method_name_node = None;
1193 let mut method_def_node = None;
1194 let mut type_name_node = None;
1195 let mut type_body_node = None;
1196 let mut type_def_node = None;
1197
1198 for cap in m.captures {
1199 let name = capture_names[cap.index as usize];
1200 match name {
1201 "fn.name" => fn_name_node = Some(cap.node),
1202 "fn.def" => fn_def_node = Some(cap.node),
1203 "method.name" => method_name_node = Some(cap.node),
1204 "method.def" => method_def_node = Some(cap.node),
1205 "type.name" => type_name_node = Some(cap.node),
1206 "type.body" => type_body_node = Some(cap.node),
1207 "type.def" => type_def_node = Some(cap.node),
1208 _ => {}
1209 }
1210 }
1211
1212 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1214 let name = node_text(source, &name_node).to_string();
1215 symbols.push(Symbol {
1216 exported: is_go_exported(&name),
1217 name,
1218 kind: SymbolKind::Function,
1219 range: node_range_with_decorators(&def_node, source, lang),
1220 signature: Some(extract_signature(source, &def_node)),
1221 scope_chain: vec![],
1222 parent: None,
1223 });
1224 }
1225
1226 if let (Some(name_node), Some(def_node)) = (method_name_node, method_def_node) {
1228 let name = node_text(source, &name_node).to_string();
1229
1230 let receiver_type = extract_go_receiver_type(&def_node, source);
1232 let scope_chain = if let Some(ref rt) = receiver_type {
1233 vec![rt.clone()]
1234 } else {
1235 vec![]
1236 };
1237
1238 symbols.push(Symbol {
1239 exported: is_go_exported(&name),
1240 name,
1241 kind: SymbolKind::Method,
1242 range: node_range_with_decorators(&def_node, source, lang),
1243 signature: Some(extract_signature(source, &def_node)),
1244 scope_chain,
1245 parent: receiver_type,
1246 });
1247 }
1248
1249 if let (Some(name_node), Some(body_node), Some(def_node)) =
1251 (type_name_node, type_body_node, type_def_node)
1252 {
1253 let name = node_text(source, &name_node).to_string();
1254 let kind = match body_node.kind() {
1255 "struct_type" => SymbolKind::Struct,
1256 "interface_type" => SymbolKind::Interface,
1257 _ => SymbolKind::TypeAlias,
1258 };
1259
1260 symbols.push(Symbol {
1261 exported: is_go_exported(&name),
1262 name,
1263 kind,
1264 range: node_range_with_decorators(&def_node, source, lang),
1265 signature: Some(extract_signature(source, &def_node)),
1266 scope_chain: vec![],
1267 parent: None,
1268 });
1269 }
1270 }
1271
1272 dedup_symbols(&mut symbols);
1273 Ok(symbols)
1274}
1275
1276fn extract_go_receiver_type(method_node: &Node, source: &str) -> Option<String> {
1279 let mut child_cursor = method_node.walk();
1281 if child_cursor.goto_first_child() {
1282 loop {
1283 let child = child_cursor.node();
1284 if child.kind() == "parameter_list" {
1285 return find_type_identifier_recursive(&child, source);
1287 }
1288 if !child_cursor.goto_next_sibling() {
1289 break;
1290 }
1291 }
1292 }
1293 None
1294}
1295
1296fn find_type_identifier_recursive(node: &Node, source: &str) -> Option<String> {
1298 if node.kind() == "type_identifier" {
1299 return Some(node_text(source, node).to_string());
1300 }
1301 let mut cursor = node.walk();
1302 if cursor.goto_first_child() {
1303 loop {
1304 if let Some(result) = find_type_identifier_recursive(&cursor.node(), source) {
1305 return Some(result);
1306 }
1307 if !cursor.goto_next_sibling() {
1308 break;
1309 }
1310 }
1311 }
1312 None
1313}
1314
1315fn extract_md_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
1319 let mut symbols = Vec::new();
1320 extract_md_sections(source, root, &mut symbols, &[]);
1321 Ok(symbols)
1322}
1323
1324fn extract_md_sections(
1326 source: &str,
1327 node: &Node,
1328 symbols: &mut Vec<Symbol>,
1329 scope_chain: &[String],
1330) {
1331 let mut cursor = node.walk();
1332 if !cursor.goto_first_child() {
1333 return;
1334 }
1335
1336 loop {
1337 let child = cursor.node();
1338 match child.kind() {
1339 "section" => {
1340 let mut section_cursor = child.walk();
1343 let mut heading_name = String::new();
1344 let mut heading_level: u8 = 0;
1345
1346 if section_cursor.goto_first_child() {
1347 loop {
1348 let section_child = section_cursor.node();
1349 if section_child.kind() == "atx_heading" {
1350 let mut h_cursor = section_child.walk();
1352 if h_cursor.goto_first_child() {
1353 loop {
1354 let h_child = h_cursor.node();
1355 let kind = h_child.kind();
1356 if kind.starts_with("atx_h") && kind.ends_with("_marker") {
1357 heading_level = kind
1359 .strip_prefix("atx_h")
1360 .and_then(|s| s.strip_suffix("_marker"))
1361 .and_then(|s| s.parse::<u8>().ok())
1362 .unwrap_or(1);
1363 } else if h_child.kind() == "inline" {
1364 heading_name =
1365 node_text(source, &h_child).trim().to_string();
1366 }
1367 if !h_cursor.goto_next_sibling() {
1368 break;
1369 }
1370 }
1371 }
1372 }
1373 if !section_cursor.goto_next_sibling() {
1374 break;
1375 }
1376 }
1377 }
1378
1379 if !heading_name.is_empty() {
1380 let range = node_range(&child);
1381 let signature =
1382 format!("{} {}", "#".repeat(heading_level as usize), heading_name);
1383
1384 symbols.push(Symbol {
1385 name: heading_name.clone(),
1386 kind: SymbolKind::Heading,
1387 range,
1388 signature: Some(signature),
1389 scope_chain: scope_chain.to_vec(),
1390 exported: false,
1391 parent: scope_chain.last().cloned(),
1392 });
1393
1394 let mut new_scope = scope_chain.to_vec();
1396 new_scope.push(heading_name);
1397 extract_md_sections(source, &child, symbols, &new_scope);
1398 }
1399 }
1400 _ => {}
1401 }
1402
1403 if !cursor.goto_next_sibling() {
1404 break;
1405 }
1406 }
1407}
1408
1409fn dedup_symbols(symbols: &mut Vec<Symbol>) {
1413 let mut seen = std::collections::HashSet::new();
1414 symbols.retain(|s| {
1415 let key = (s.name.clone(), format!("{:?}", s.kind), s.range.start_line);
1416 seen.insert(key)
1417 });
1418}
1419
1420pub struct TreeSitterProvider {
1423 parser: RefCell<FileParser>,
1424}
1425
1426#[derive(Debug, Clone)]
1427struct ReExportTarget {
1428 file: PathBuf,
1429 symbol_name: String,
1430}
1431
1432impl TreeSitterProvider {
1433 pub fn new() -> Self {
1435 Self {
1436 parser: RefCell::new(FileParser::new()),
1437 }
1438 }
1439
1440 fn resolve_symbol_inner(
1441 &self,
1442 file: &Path,
1443 name: &str,
1444 depth: usize,
1445 visited: &mut HashSet<(PathBuf, String)>,
1446 ) -> Result<Vec<SymbolMatch>, AftError> {
1447 if depth > MAX_REEXPORT_DEPTH {
1448 return Ok(Vec::new());
1449 }
1450
1451 let canonical_file = std::fs::canonicalize(file).unwrap_or_else(|_| file.to_path_buf());
1452 if !visited.insert((canonical_file, name.to_string())) {
1453 return Ok(Vec::new());
1454 }
1455
1456 let symbols = self.parser.borrow_mut().extract_symbols(file)?;
1457 let local_matches = symbol_matches_in_file(file, &symbols, name);
1458 if !local_matches.is_empty() {
1459 return Ok(local_matches);
1460 }
1461
1462 if name == "default" {
1463 let default_matches = self.resolve_local_default_export(file, &symbols)?;
1464 if !default_matches.is_empty() {
1465 return Ok(default_matches);
1466 }
1467 }
1468
1469 let reexport_targets = self.collect_reexport_targets(file, name)?;
1470 let mut matches = Vec::new();
1471 let mut seen = HashSet::new();
1472 for target in reexport_targets {
1473 for resolved in
1474 self.resolve_symbol_inner(&target.file, &target.symbol_name, depth + 1, visited)?
1475 {
1476 let key = format!(
1477 "{}:{}:{}:{}:{}:{}",
1478 resolved.file,
1479 resolved.symbol.name,
1480 resolved.symbol.range.start_line,
1481 resolved.symbol.range.start_col,
1482 resolved.symbol.range.end_line,
1483 resolved.symbol.range.end_col
1484 );
1485 if seen.insert(key) {
1486 matches.push(resolved);
1487 }
1488 }
1489 }
1490
1491 Ok(matches)
1492 }
1493
1494 fn collect_reexport_targets(
1495 &self,
1496 file: &Path,
1497 requested_name: &str,
1498 ) -> Result<Vec<ReExportTarget>, AftError> {
1499 let (source, tree, lang) = self.read_parsed_file(file)?;
1500 if !matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
1501 return Ok(Vec::new());
1502 }
1503
1504 let mut targets = Vec::new();
1505 let root = tree.root_node();
1506 let from_dir = file.parent().unwrap_or_else(|| Path::new("."));
1507
1508 let mut cursor = root.walk();
1509 if !cursor.goto_first_child() {
1510 return Ok(targets);
1511 }
1512
1513 loop {
1514 let node = cursor.node();
1515 if node.kind() == "export_statement" {
1516 let Some(source_node) = node.child_by_field_name("source") else {
1517 if !cursor.goto_next_sibling() {
1518 break;
1519 }
1520 continue;
1521 };
1522
1523 let Some(module_path) = string_content(&source, &source_node) else {
1524 if !cursor.goto_next_sibling() {
1525 break;
1526 }
1527 continue;
1528 };
1529
1530 let Some(target_file) = resolve_module_path(from_dir, &module_path) else {
1531 if !cursor.goto_next_sibling() {
1532 break;
1533 }
1534 continue;
1535 };
1536
1537 if let Some(export_clause) = find_child_by_kind(node, "export_clause") {
1538 if let Some(symbol_name) =
1539 resolve_export_clause_name(&source, &export_clause, requested_name)
1540 {
1541 targets.push(ReExportTarget {
1542 file: target_file,
1543 symbol_name,
1544 });
1545 }
1546 } else if export_statement_has_wildcard(&source, &node) {
1547 targets.push(ReExportTarget {
1548 file: target_file,
1549 symbol_name: requested_name.to_string(),
1550 });
1551 }
1552 }
1553
1554 if !cursor.goto_next_sibling() {
1555 break;
1556 }
1557 }
1558
1559 Ok(targets)
1560 }
1561
1562 fn resolve_local_default_export(
1563 &self,
1564 file: &Path,
1565 symbols: &[Symbol],
1566 ) -> Result<Vec<SymbolMatch>, AftError> {
1567 let (source, tree, lang) = self.read_parsed_file(file)?;
1568 if !matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
1569 return Ok(Vec::new());
1570 }
1571
1572 let root = tree.root_node();
1573 let mut matches = Vec::new();
1574 let mut seen = HashSet::new();
1575
1576 let mut cursor = root.walk();
1577 if !cursor.goto_first_child() {
1578 return Ok(matches);
1579 }
1580
1581 loop {
1582 let node = cursor.node();
1583 if node.kind() == "export_statement"
1584 && node.child_by_field_name("source").is_none()
1585 && node_contains_token(&source, &node, "default")
1586 {
1587 if let Some(target_name) = default_export_target_name(&source, &node) {
1588 for symbol_match in symbol_matches_in_file(file, symbols, &target_name) {
1589 let key = format!(
1590 "{}:{}:{}:{}:{}:{}",
1591 symbol_match.file,
1592 symbol_match.symbol.name,
1593 symbol_match.symbol.range.start_line,
1594 symbol_match.symbol.range.start_col,
1595 symbol_match.symbol.range.end_line,
1596 symbol_match.symbol.range.end_col
1597 );
1598 if seen.insert(key) {
1599 matches.push(symbol_match);
1600 }
1601 }
1602 }
1603 }
1604
1605 if !cursor.goto_next_sibling() {
1606 break;
1607 }
1608 }
1609
1610 Ok(matches)
1611 }
1612
1613 fn read_parsed_file(&self, file: &Path) -> Result<(String, Tree, LangId), AftError> {
1614 let source = std::fs::read_to_string(file).map_err(|e| AftError::FileNotFound {
1615 path: format!("{}: {}", file.display(), e),
1616 })?;
1617 let (tree, lang) = {
1618 let mut parser = self.parser.borrow_mut();
1619 parser.parse_cloned(file)?
1620 };
1621 Ok((source, tree, lang))
1622 }
1623}
1624
1625fn symbol_matches_in_file(file: &Path, symbols: &[Symbol], name: &str) -> Vec<SymbolMatch> {
1626 symbols
1627 .iter()
1628 .filter(|symbol| symbol.name == name)
1629 .cloned()
1630 .map(|symbol| SymbolMatch {
1631 file: file.display().to_string(),
1632 symbol,
1633 })
1634 .collect()
1635}
1636
1637fn string_content(source: &str, node: &Node) -> Option<String> {
1638 let text = node_text(source, node);
1639 if text.len() < 2 {
1640 return None;
1641 }
1642
1643 Some(
1644 text.trim_start_matches(|c| c == '\'' || c == '"')
1645 .trim_end_matches(|c| c == '\'' || c == '"')
1646 .to_string(),
1647 )
1648}
1649
1650fn find_child_by_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
1651 let mut cursor = node.walk();
1652 if !cursor.goto_first_child() {
1653 return None;
1654 }
1655
1656 loop {
1657 let child = cursor.node();
1658 if child.kind() == kind {
1659 return Some(child);
1660 }
1661 if !cursor.goto_next_sibling() {
1662 break;
1663 }
1664 }
1665
1666 None
1667}
1668
1669fn resolve_export_clause_name(
1670 source: &str,
1671 export_clause: &Node,
1672 requested_name: &str,
1673) -> Option<String> {
1674 let mut cursor = export_clause.walk();
1675 if !cursor.goto_first_child() {
1676 return None;
1677 }
1678
1679 loop {
1680 let child = cursor.node();
1681 if child.kind() == "export_specifier" {
1682 let (source_name, exported_name) = export_specifier_names(source, &child)?;
1683 if exported_name == requested_name {
1684 return Some(source_name);
1685 }
1686 }
1687
1688 if !cursor.goto_next_sibling() {
1689 break;
1690 }
1691 }
1692
1693 None
1694}
1695
1696fn export_specifier_names(source: &str, specifier: &Node) -> Option<(String, String)> {
1697 let source_name = specifier
1698 .child_by_field_name("name")
1699 .map(|node| node_text(source, &node).to_string());
1700 let alias_name = specifier
1701 .child_by_field_name("alias")
1702 .map(|node| node_text(source, &node).to_string());
1703
1704 if let Some(source_name) = source_name {
1705 let exported_name = alias_name.unwrap_or_else(|| source_name.clone());
1706 return Some((source_name, exported_name));
1707 }
1708
1709 let mut names = Vec::new();
1710 let mut cursor = specifier.walk();
1711 if cursor.goto_first_child() {
1712 loop {
1713 let child = cursor.node();
1714 let child_text = node_text(source, &child).trim();
1715 if matches!(
1716 child.kind(),
1717 "identifier" | "type_identifier" | "property_identifier"
1718 ) || child_text == "default"
1719 {
1720 names.push(child_text.to_string());
1721 }
1722 if !cursor.goto_next_sibling() {
1723 break;
1724 }
1725 }
1726 }
1727
1728 match names.as_slice() {
1729 [name] => Some((name.clone(), name.clone())),
1730 [source_name, exported_name, ..] => Some((source_name.clone(), exported_name.clone())),
1731 _ => None,
1732 }
1733}
1734
1735fn export_statement_has_wildcard(source: &str, node: &Node) -> bool {
1736 let mut cursor = node.walk();
1737 if !cursor.goto_first_child() {
1738 return false;
1739 }
1740
1741 loop {
1742 if node_text(source, &cursor.node()).trim() == "*" {
1743 return true;
1744 }
1745 if !cursor.goto_next_sibling() {
1746 break;
1747 }
1748 }
1749
1750 false
1751}
1752
1753fn node_contains_token(source: &str, node: &Node, token: &str) -> bool {
1754 let mut cursor = node.walk();
1755 if !cursor.goto_first_child() {
1756 return false;
1757 }
1758
1759 loop {
1760 if node_text(source, &cursor.node()).trim() == token {
1761 return true;
1762 }
1763 if !cursor.goto_next_sibling() {
1764 break;
1765 }
1766 }
1767
1768 false
1769}
1770
1771fn default_export_target_name(source: &str, export_stmt: &Node) -> Option<String> {
1772 let mut cursor = export_stmt.walk();
1773 if !cursor.goto_first_child() {
1774 return None;
1775 }
1776
1777 loop {
1778 let child = cursor.node();
1779 match child.kind() {
1780 "function_declaration"
1781 | "class_declaration"
1782 | "interface_declaration"
1783 | "enum_declaration"
1784 | "type_alias_declaration"
1785 | "lexical_declaration" => {
1786 if let Some(name_node) = child.child_by_field_name("name") {
1787 return Some(node_text(source, &name_node).to_string());
1788 }
1789
1790 if child.kind() == "lexical_declaration" {
1791 let mut child_cursor = child.walk();
1792 if child_cursor.goto_first_child() {
1793 loop {
1794 let nested = child_cursor.node();
1795 if nested.kind() == "variable_declarator" {
1796 if let Some(name_node) = nested.child_by_field_name("name") {
1797 return Some(node_text(source, &name_node).to_string());
1798 }
1799 }
1800 if !child_cursor.goto_next_sibling() {
1801 break;
1802 }
1803 }
1804 }
1805 }
1806 }
1807 "identifier" | "type_identifier" => {
1808 let text = node_text(source, &child);
1809 if text != "export" && text != "default" {
1810 return Some(text.to_string());
1811 }
1812 }
1813 _ => {}
1814 }
1815
1816 if !cursor.goto_next_sibling() {
1817 break;
1818 }
1819 }
1820
1821 None
1822}
1823
1824impl crate::language::LanguageProvider for TreeSitterProvider {
1825 fn resolve_symbol(&self, file: &Path, name: &str) -> Result<Vec<SymbolMatch>, AftError> {
1826 let matches = self.resolve_symbol_inner(file, name, 0, &mut HashSet::new())?;
1827
1828 if matches.is_empty() {
1829 Err(AftError::SymbolNotFound {
1830 name: name.to_string(),
1831 file: file.display().to_string(),
1832 })
1833 } else {
1834 Ok(matches)
1835 }
1836 }
1837
1838 fn list_symbols(&self, file: &Path) -> Result<Vec<Symbol>, AftError> {
1839 self.parser.borrow_mut().extract_symbols(file)
1840 }
1841}
1842
1843#[cfg(test)]
1844mod tests {
1845 use super::*;
1846 use crate::language::LanguageProvider;
1847 use std::path::PathBuf;
1848
1849 fn fixture_path(name: &str) -> PathBuf {
1850 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
1851 .join("tests")
1852 .join("fixtures")
1853 .join(name)
1854 }
1855
1856 #[test]
1859 fn detect_ts() {
1860 assert_eq!(
1861 detect_language(Path::new("foo.ts")),
1862 Some(LangId::TypeScript)
1863 );
1864 }
1865
1866 #[test]
1867 fn detect_tsx() {
1868 assert_eq!(detect_language(Path::new("foo.tsx")), Some(LangId::Tsx));
1869 }
1870
1871 #[test]
1872 fn detect_js() {
1873 assert_eq!(
1874 detect_language(Path::new("foo.js")),
1875 Some(LangId::JavaScript)
1876 );
1877 }
1878
1879 #[test]
1880 fn detect_jsx() {
1881 assert_eq!(
1882 detect_language(Path::new("foo.jsx")),
1883 Some(LangId::JavaScript)
1884 );
1885 }
1886
1887 #[test]
1888 fn detect_py() {
1889 assert_eq!(detect_language(Path::new("foo.py")), Some(LangId::Python));
1890 }
1891
1892 #[test]
1893 fn detect_rs() {
1894 assert_eq!(detect_language(Path::new("foo.rs")), Some(LangId::Rust));
1895 }
1896
1897 #[test]
1898 fn detect_go() {
1899 assert_eq!(detect_language(Path::new("foo.go")), Some(LangId::Go));
1900 }
1901
1902 #[test]
1903 fn detect_unknown_returns_none() {
1904 assert_eq!(detect_language(Path::new("foo.txt")), None);
1905 }
1906
1907 #[test]
1910 fn unsupported_extension_returns_invalid_request() {
1911 let path = fixture_path("sample.ts");
1913 let bad_path = path.with_extension("txt");
1914 std::fs::write(&bad_path, "hello").unwrap();
1916 let provider = TreeSitterProvider::new();
1917 let result = provider.list_symbols(&bad_path);
1918 std::fs::remove_file(&bad_path).ok();
1919 match result {
1920 Err(AftError::InvalidRequest { message }) => {
1921 assert!(
1922 message.contains("unsupported file extension"),
1923 "msg: {}",
1924 message
1925 );
1926 assert!(message.contains("txt"), "msg: {}", message);
1927 }
1928 other => panic!("expected InvalidRequest, got {:?}", other),
1929 }
1930 }
1931
1932 #[test]
1935 fn ts_extracts_all_symbol_kinds() {
1936 let provider = TreeSitterProvider::new();
1937 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
1938
1939 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
1940 assert!(
1941 names.contains(&"greet"),
1942 "missing function greet: {:?}",
1943 names
1944 );
1945 assert!(names.contains(&"add"), "missing arrow fn add: {:?}", names);
1946 assert!(
1947 names.contains(&"UserService"),
1948 "missing class UserService: {:?}",
1949 names
1950 );
1951 assert!(
1952 names.contains(&"Config"),
1953 "missing interface Config: {:?}",
1954 names
1955 );
1956 assert!(
1957 names.contains(&"Status"),
1958 "missing enum Status: {:?}",
1959 names
1960 );
1961 assert!(
1962 names.contains(&"UserId"),
1963 "missing type alias UserId: {:?}",
1964 names
1965 );
1966 assert!(
1967 names.contains(&"internalHelper"),
1968 "missing non-exported fn: {:?}",
1969 names
1970 );
1971
1972 assert!(
1974 symbols.len() >= 6,
1975 "expected ≥6 symbols, got {}: {:?}",
1976 symbols.len(),
1977 names
1978 );
1979 }
1980
1981 #[test]
1982 fn ts_symbol_kinds_correct() {
1983 let provider = TreeSitterProvider::new();
1984 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
1985
1986 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
1987
1988 assert_eq!(find("greet").kind, SymbolKind::Function);
1989 assert_eq!(find("add").kind, SymbolKind::Function); assert_eq!(find("UserService").kind, SymbolKind::Class);
1991 assert_eq!(find("Config").kind, SymbolKind::Interface);
1992 assert_eq!(find("Status").kind, SymbolKind::Enum);
1993 assert_eq!(find("UserId").kind, SymbolKind::TypeAlias);
1994 }
1995
1996 #[test]
1997 fn ts_export_detection() {
1998 let provider = TreeSitterProvider::new();
1999 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
2000
2001 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
2002
2003 assert!(find("greet").exported, "greet should be exported");
2004 assert!(find("add").exported, "add should be exported");
2005 assert!(
2006 find("UserService").exported,
2007 "UserService should be exported"
2008 );
2009 assert!(find("Config").exported, "Config should be exported");
2010 assert!(find("Status").exported, "Status should be exported");
2011 assert!(
2012 !find("internalHelper").exported,
2013 "internalHelper should not be exported"
2014 );
2015 }
2016
2017 #[test]
2018 fn ts_method_scope_chain() {
2019 let provider = TreeSitterProvider::new();
2020 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
2021
2022 let methods: Vec<&Symbol> = symbols
2023 .iter()
2024 .filter(|s| s.kind == SymbolKind::Method)
2025 .collect();
2026 assert!(!methods.is_empty(), "should have at least one method");
2027
2028 for method in &methods {
2029 assert_eq!(
2030 method.scope_chain,
2031 vec!["UserService"],
2032 "method {} should have UserService in scope chain",
2033 method.name
2034 );
2035 assert_eq!(method.parent.as_deref(), Some("UserService"));
2036 }
2037 }
2038
2039 #[test]
2040 fn ts_signatures_present() {
2041 let provider = TreeSitterProvider::new();
2042 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
2043
2044 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
2045
2046 let greet_sig = find("greet").signature.as_ref().unwrap();
2047 assert!(
2048 greet_sig.contains("greet"),
2049 "signature should contain function name: {}",
2050 greet_sig
2051 );
2052 }
2053
2054 #[test]
2055 fn ts_ranges_valid() {
2056 let provider = TreeSitterProvider::new();
2057 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
2058
2059 for s in &symbols {
2060 assert!(
2061 s.range.end_line >= s.range.start_line,
2062 "symbol {} has invalid range: {:?}",
2063 s.name,
2064 s.range
2065 );
2066 }
2067 }
2068
2069 #[test]
2072 fn js_extracts_core_symbols() {
2073 let provider = TreeSitterProvider::new();
2074 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
2075
2076 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
2077 assert!(
2078 names.contains(&"multiply"),
2079 "missing function multiply: {:?}",
2080 names
2081 );
2082 assert!(
2083 names.contains(&"divide"),
2084 "missing arrow fn divide: {:?}",
2085 names
2086 );
2087 assert!(
2088 names.contains(&"EventEmitter"),
2089 "missing class EventEmitter: {:?}",
2090 names
2091 );
2092 assert!(
2093 names.contains(&"main"),
2094 "missing default export fn main: {:?}",
2095 names
2096 );
2097
2098 assert!(
2099 symbols.len() >= 4,
2100 "expected ≥4 symbols, got {}: {:?}",
2101 symbols.len(),
2102 names
2103 );
2104 }
2105
2106 #[test]
2107 fn js_arrow_fn_correctly_named() {
2108 let provider = TreeSitterProvider::new();
2109 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
2110
2111 let divide = symbols.iter().find(|s| s.name == "divide").unwrap();
2112 assert_eq!(divide.kind, SymbolKind::Function);
2113 assert!(divide.exported, "divide should be exported");
2114
2115 let internal = symbols.iter().find(|s| s.name == "internalUtil").unwrap();
2116 assert_eq!(internal.kind, SymbolKind::Function);
2117 assert!(!internal.exported, "internalUtil should not be exported");
2118 }
2119
2120 #[test]
2121 fn js_method_scope_chain() {
2122 let provider = TreeSitterProvider::new();
2123 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
2124
2125 let methods: Vec<&Symbol> = symbols
2126 .iter()
2127 .filter(|s| s.kind == SymbolKind::Method)
2128 .collect();
2129
2130 for method in &methods {
2131 assert_eq!(
2132 method.scope_chain,
2133 vec!["EventEmitter"],
2134 "method {} should have EventEmitter in scope chain",
2135 method.name
2136 );
2137 }
2138 }
2139
2140 #[test]
2143 fn tsx_extracts_react_component() {
2144 let provider = TreeSitterProvider::new();
2145 let symbols = provider.list_symbols(&fixture_path("sample.tsx")).unwrap();
2146
2147 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
2148 assert!(
2149 names.contains(&"Button"),
2150 "missing React component Button: {:?}",
2151 names
2152 );
2153 assert!(
2154 names.contains(&"Counter"),
2155 "missing class Counter: {:?}",
2156 names
2157 );
2158 assert!(
2159 names.contains(&"formatLabel"),
2160 "missing function formatLabel: {:?}",
2161 names
2162 );
2163
2164 assert!(
2165 symbols.len() >= 2,
2166 "expected ≥2 symbols, got {}: {:?}",
2167 symbols.len(),
2168 names
2169 );
2170 }
2171
2172 #[test]
2173 fn tsx_jsx_doesnt_break_parser() {
2174 let provider = TreeSitterProvider::new();
2176 let result = provider.list_symbols(&fixture_path("sample.tsx"));
2177 assert!(
2178 result.is_ok(),
2179 "TSX parsing should succeed: {:?}",
2180 result.err()
2181 );
2182 }
2183
2184 #[test]
2187 fn resolve_symbol_finds_match() {
2188 let provider = TreeSitterProvider::new();
2189 let matches = provider
2190 .resolve_symbol(&fixture_path("sample.ts"), "greet")
2191 .unwrap();
2192 assert_eq!(matches.len(), 1);
2193 assert_eq!(matches[0].symbol.name, "greet");
2194 assert_eq!(matches[0].symbol.kind, SymbolKind::Function);
2195 }
2196
2197 #[test]
2198 fn resolve_symbol_not_found() {
2199 let provider = TreeSitterProvider::new();
2200 let result = provider.resolve_symbol(&fixture_path("sample.ts"), "nonexistent");
2201 assert!(matches!(result, Err(AftError::SymbolNotFound { .. })));
2202 }
2203
2204 #[test]
2205 fn resolve_symbol_follows_reexport_chains() {
2206 let dir = tempfile::tempdir().unwrap();
2207 let config = dir.path().join("config.ts");
2208 let barrel1 = dir.path().join("barrel1.ts");
2209 let barrel2 = dir.path().join("barrel2.ts");
2210 let barrel3 = dir.path().join("barrel3.ts");
2211 let index = dir.path().join("index.ts");
2212
2213 std::fs::write(
2214 &config,
2215 "export class Config {}\nexport default class DefaultConfig {}\n",
2216 )
2217 .unwrap();
2218 std::fs::write(
2219 &barrel1,
2220 "export { Config } from './config';\nexport { default as NamedDefault } from './config';\n",
2221 )
2222 .unwrap();
2223 std::fs::write(
2224 &barrel2,
2225 "export { Config as RenamedConfig } from './barrel1';\n",
2226 )
2227 .unwrap();
2228 std::fs::write(
2229 &barrel3,
2230 "export * from './barrel2';\nexport * from './barrel1';\n",
2231 )
2232 .unwrap();
2233 std::fs::write(
2234 &index,
2235 "export class Config {}\nexport { RenamedConfig as FinalConfig } from './barrel3';\nexport * from './barrel3';\n",
2236 )
2237 .unwrap();
2238
2239 let provider = TreeSitterProvider::new();
2240 let config_canon = std::fs::canonicalize(&config).unwrap();
2241
2242 let direct = provider.resolve_symbol(&barrel1, "Config").unwrap();
2243 assert_eq!(direct.len(), 1);
2244 assert_eq!(direct[0].symbol.name, "Config");
2245 assert_eq!(direct[0].file, config_canon.display().to_string());
2246
2247 let renamed = provider.resolve_symbol(&barrel2, "RenamedConfig").unwrap();
2248 assert_eq!(renamed.len(), 1);
2249 assert_eq!(renamed[0].symbol.name, "Config");
2250 assert_eq!(renamed[0].file, config_canon.display().to_string());
2251
2252 let wildcard_chain = provider.resolve_symbol(&index, "FinalConfig").unwrap();
2253 assert_eq!(wildcard_chain.len(), 1);
2254 assert_eq!(wildcard_chain[0].symbol.name, "Config");
2255 assert_eq!(wildcard_chain[0].file, config_canon.display().to_string());
2256
2257 let wildcard_default = provider.resolve_symbol(&index, "NamedDefault").unwrap();
2258 assert_eq!(wildcard_default.len(), 1);
2259 assert_eq!(wildcard_default[0].symbol.name, "DefaultConfig");
2260 assert_eq!(wildcard_default[0].file, config_canon.display().to_string());
2261
2262 let local = provider.resolve_symbol(&index, "Config").unwrap();
2263 assert_eq!(local.len(), 1);
2264 assert_eq!(local[0].symbol.name, "Config");
2265 assert_eq!(local[0].file, index.display().to_string());
2266 }
2267
2268 #[test]
2271 fn symbol_range_includes_rust_attributes() {
2272 let dir = tempfile::tempdir().unwrap();
2273 let path = dir.path().join("test_attrs.rs");
2274 std::fs::write(
2275 &path,
2276 "/// This is a doc comment\n#[test]\n#[cfg(test)]\nfn my_test_fn() {\n assert!(true);\n}\n",
2277 )
2278 .unwrap();
2279
2280 let provider = TreeSitterProvider::new();
2281 let matches = provider.resolve_symbol(&path, "my_test_fn").unwrap();
2282 assert_eq!(matches.len(), 1);
2283 assert_eq!(
2284 matches[0].symbol.range.start_line, 0,
2285 "symbol range should include preceding /// doc comment, got start_line={}",
2286 matches[0].symbol.range.start_line
2287 );
2288 }
2289
2290 #[test]
2291 fn symbol_range_includes_go_doc_comment() {
2292 let dir = tempfile::tempdir().unwrap();
2293 let path = dir.path().join("test_doc.go");
2294 std::fs::write(
2295 &path,
2296 "package main\n\n// MyFunc does something useful.\n// It has a multi-line doc.\nfunc MyFunc() {\n}\n",
2297 )
2298 .unwrap();
2299
2300 let provider = TreeSitterProvider::new();
2301 let matches = provider.resolve_symbol(&path, "MyFunc").unwrap();
2302 assert_eq!(matches.len(), 1);
2303 assert_eq!(
2304 matches[0].symbol.range.start_line, 2,
2305 "symbol range should include preceding doc comments, got start_line={}",
2306 matches[0].symbol.range.start_line
2307 );
2308 }
2309
2310 #[test]
2311 fn symbol_range_skips_unrelated_comments() {
2312 let dir = tempfile::tempdir().unwrap();
2313 let path = dir.path().join("test_gap.go");
2314 std::fs::write(
2315 &path,
2316 "package main\n\n// This is a standalone comment\n\nfunc Standalone() {\n}\n",
2317 )
2318 .unwrap();
2319
2320 let provider = TreeSitterProvider::new();
2321 let matches = provider.resolve_symbol(&path, "Standalone").unwrap();
2322 assert_eq!(matches.len(), 1);
2323 assert_eq!(
2324 matches[0].symbol.range.start_line, 4,
2325 "symbol range should NOT include comment separated by blank line, got start_line={}",
2326 matches[0].symbol.range.start_line
2327 );
2328 }
2329
2330 #[test]
2331 fn parse_cache_returns_same_tree() {
2332 let mut parser = FileParser::new();
2333 let path = fixture_path("sample.ts");
2334
2335 let (tree1, _) = parser.parse(&path).unwrap();
2336 let tree1_root = tree1.root_node().byte_range();
2337
2338 let (tree2, _) = parser.parse(&path).unwrap();
2339 let tree2_root = tree2.root_node().byte_range();
2340
2341 assert_eq!(tree1_root, tree2_root);
2343 }
2344
2345 #[test]
2348 fn py_extracts_all_symbols() {
2349 let provider = TreeSitterProvider::new();
2350 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
2351
2352 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
2353 assert!(
2354 names.contains(&"top_level_function"),
2355 "missing top_level_function: {:?}",
2356 names
2357 );
2358 assert!(names.contains(&"MyClass"), "missing MyClass: {:?}", names);
2359 assert!(
2360 names.contains(&"instance_method"),
2361 "missing method instance_method: {:?}",
2362 names
2363 );
2364 assert!(
2365 names.contains(&"decorated_function"),
2366 "missing decorated_function: {:?}",
2367 names
2368 );
2369
2370 assert!(
2372 symbols.len() >= 4,
2373 "expected ≥4 symbols, got {}: {:?}",
2374 symbols.len(),
2375 names
2376 );
2377 }
2378
2379 #[test]
2380 fn py_symbol_kinds_correct() {
2381 let provider = TreeSitterProvider::new();
2382 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
2383
2384 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
2385
2386 assert_eq!(find("top_level_function").kind, SymbolKind::Function);
2387 assert_eq!(find("MyClass").kind, SymbolKind::Class);
2388 assert_eq!(find("instance_method").kind, SymbolKind::Method);
2389 assert_eq!(find("decorated_function").kind, SymbolKind::Function);
2390 assert_eq!(find("OuterClass").kind, SymbolKind::Class);
2391 assert_eq!(find("InnerClass").kind, SymbolKind::Class);
2392 assert_eq!(find("inner_method").kind, SymbolKind::Method);
2393 assert_eq!(find("outer_method").kind, SymbolKind::Method);
2394 }
2395
2396 #[test]
2397 fn py_method_scope_chain() {
2398 let provider = TreeSitterProvider::new();
2399 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
2400
2401 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
2402
2403 assert_eq!(
2405 find("instance_method").scope_chain,
2406 vec!["MyClass"],
2407 "instance_method should have MyClass in scope chain"
2408 );
2409 assert_eq!(find("instance_method").parent.as_deref(), Some("MyClass"));
2410
2411 assert_eq!(
2413 find("inner_method").scope_chain,
2414 vec!["OuterClass", "InnerClass"],
2415 "inner_method should have nested scope chain"
2416 );
2417
2418 assert_eq!(
2420 find("InnerClass").scope_chain,
2421 vec!["OuterClass"],
2422 "InnerClass should have OuterClass in scope"
2423 );
2424
2425 assert!(
2427 find("top_level_function").scope_chain.is_empty(),
2428 "top-level function should have empty scope chain"
2429 );
2430 }
2431
2432 #[test]
2433 fn py_decorated_function_signature() {
2434 let provider = TreeSitterProvider::new();
2435 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
2436
2437 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
2438
2439 let sig = find("decorated_function").signature.as_ref().unwrap();
2440 assert!(
2441 sig.contains("@staticmethod"),
2442 "decorated function signature should include decorator: {}",
2443 sig
2444 );
2445 assert!(
2446 sig.contains("def decorated_function"),
2447 "signature should include function def: {}",
2448 sig
2449 );
2450 }
2451
2452 #[test]
2453 fn py_ranges_valid() {
2454 let provider = TreeSitterProvider::new();
2455 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
2456
2457 for s in &symbols {
2458 assert!(
2459 s.range.end_line >= s.range.start_line,
2460 "symbol {} has invalid range: {:?}",
2461 s.name,
2462 s.range
2463 );
2464 }
2465 }
2466
2467 #[test]
2470 fn rs_extracts_all_symbols() {
2471 let provider = TreeSitterProvider::new();
2472 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
2473
2474 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
2475 assert!(
2476 names.contains(&"public_function"),
2477 "missing public_function: {:?}",
2478 names
2479 );
2480 assert!(
2481 names.contains(&"private_function"),
2482 "missing private_function: {:?}",
2483 names
2484 );
2485 assert!(names.contains(&"MyStruct"), "missing MyStruct: {:?}", names);
2486 assert!(names.contains(&"Color"), "missing enum Color: {:?}", names);
2487 assert!(
2488 names.contains(&"Drawable"),
2489 "missing trait Drawable: {:?}",
2490 names
2491 );
2492 assert!(
2494 names.contains(&"new"),
2495 "missing impl method new: {:?}",
2496 names
2497 );
2498
2499 assert!(
2501 symbols.len() >= 6,
2502 "expected ≥6 symbols, got {}: {:?}",
2503 symbols.len(),
2504 names
2505 );
2506 }
2507
2508 #[test]
2509 fn rs_symbol_kinds_correct() {
2510 let provider = TreeSitterProvider::new();
2511 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
2512
2513 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
2514
2515 assert_eq!(find("public_function").kind, SymbolKind::Function);
2516 assert_eq!(find("private_function").kind, SymbolKind::Function);
2517 assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
2518 assert_eq!(find("Color").kind, SymbolKind::Enum);
2519 assert_eq!(find("Drawable").kind, SymbolKind::Interface); assert_eq!(find("new").kind, SymbolKind::Method);
2521 }
2522
2523 #[test]
2524 fn rs_pub_export_detection() {
2525 let provider = TreeSitterProvider::new();
2526 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
2527
2528 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
2529
2530 assert!(
2531 find("public_function").exported,
2532 "pub fn should be exported"
2533 );
2534 assert!(
2535 !find("private_function").exported,
2536 "non-pub fn should not be exported"
2537 );
2538 assert!(find("MyStruct").exported, "pub struct should be exported");
2539 assert!(find("Color").exported, "pub enum should be exported");
2540 assert!(find("Drawable").exported, "pub trait should be exported");
2541 assert!(
2542 find("new").exported,
2543 "pub fn inside impl should be exported"
2544 );
2545 assert!(
2546 !find("helper").exported,
2547 "non-pub fn inside impl should not be exported"
2548 );
2549 }
2550
2551 #[test]
2552 fn rs_impl_method_scope_chain() {
2553 let provider = TreeSitterProvider::new();
2554 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
2555
2556 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
2557
2558 assert_eq!(
2560 find("new").scope_chain,
2561 vec!["MyStruct"],
2562 "impl method should have type in scope chain"
2563 );
2564 assert_eq!(find("new").parent.as_deref(), Some("MyStruct"));
2565
2566 assert!(
2568 find("public_function").scope_chain.is_empty(),
2569 "free function should have empty scope chain"
2570 );
2571 }
2572
2573 #[test]
2574 fn rs_trait_impl_scope_chain() {
2575 let provider = TreeSitterProvider::new();
2576 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
2577
2578 let draw = symbols.iter().find(|s| s.name == "draw").unwrap();
2580 assert_eq!(
2581 draw.scope_chain,
2582 vec!["Drawable for MyStruct"],
2583 "trait impl method should have 'Trait for Type' scope"
2584 );
2585 assert_eq!(draw.parent.as_deref(), Some("MyStruct"));
2586 }
2587
2588 #[test]
2589 fn rs_ranges_valid() {
2590 let provider = TreeSitterProvider::new();
2591 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
2592
2593 for s in &symbols {
2594 assert!(
2595 s.range.end_line >= s.range.start_line,
2596 "symbol {} has invalid range: {:?}",
2597 s.name,
2598 s.range
2599 );
2600 }
2601 }
2602
2603 #[test]
2606 fn go_extracts_all_symbols() {
2607 let provider = TreeSitterProvider::new();
2608 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
2609
2610 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
2611 assert!(
2612 names.contains(&"ExportedFunction"),
2613 "missing ExportedFunction: {:?}",
2614 names
2615 );
2616 assert!(
2617 names.contains(&"unexportedFunction"),
2618 "missing unexportedFunction: {:?}",
2619 names
2620 );
2621 assert!(
2622 names.contains(&"MyStruct"),
2623 "missing struct MyStruct: {:?}",
2624 names
2625 );
2626 assert!(
2627 names.contains(&"Reader"),
2628 "missing interface Reader: {:?}",
2629 names
2630 );
2631 assert!(
2633 names.contains(&"String"),
2634 "missing receiver method String: {:?}",
2635 names
2636 );
2637
2638 assert!(
2640 symbols.len() >= 4,
2641 "expected ≥4 symbols, got {}: {:?}",
2642 symbols.len(),
2643 names
2644 );
2645 }
2646
2647 #[test]
2648 fn go_symbol_kinds_correct() {
2649 let provider = TreeSitterProvider::new();
2650 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
2651
2652 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
2653
2654 assert_eq!(find("ExportedFunction").kind, SymbolKind::Function);
2655 assert_eq!(find("unexportedFunction").kind, SymbolKind::Function);
2656 assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
2657 assert_eq!(find("Reader").kind, SymbolKind::Interface);
2658 assert_eq!(find("String").kind, SymbolKind::Method);
2659 assert_eq!(find("helper").kind, SymbolKind::Method);
2660 }
2661
2662 #[test]
2663 fn go_uppercase_export_detection() {
2664 let provider = TreeSitterProvider::new();
2665 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
2666
2667 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
2668
2669 assert!(
2670 find("ExportedFunction").exported,
2671 "ExportedFunction (uppercase) should be exported"
2672 );
2673 assert!(
2674 !find("unexportedFunction").exported,
2675 "unexportedFunction (lowercase) should not be exported"
2676 );
2677 assert!(
2678 find("MyStruct").exported,
2679 "MyStruct (uppercase) should be exported"
2680 );
2681 assert!(
2682 find("Reader").exported,
2683 "Reader (uppercase) should be exported"
2684 );
2685 assert!(
2686 find("String").exported,
2687 "String method (uppercase) should be exported"
2688 );
2689 assert!(
2690 !find("helper").exported,
2691 "helper method (lowercase) should not be exported"
2692 );
2693 }
2694
2695 #[test]
2696 fn go_receiver_method_scope_chain() {
2697 let provider = TreeSitterProvider::new();
2698 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
2699
2700 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
2701
2702 assert_eq!(
2704 find("String").scope_chain,
2705 vec!["MyStruct"],
2706 "receiver method should have type in scope chain"
2707 );
2708 assert_eq!(find("String").parent.as_deref(), Some("MyStruct"));
2709
2710 assert!(
2712 find("ExportedFunction").scope_chain.is_empty(),
2713 "regular function should have empty scope chain"
2714 );
2715 }
2716
2717 #[test]
2718 fn go_ranges_valid() {
2719 let provider = TreeSitterProvider::new();
2720 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
2721
2722 for s in &symbols {
2723 assert!(
2724 s.range.end_line >= s.range.start_line,
2725 "symbol {} has invalid range: {:?}",
2726 s.name,
2727 s.range
2728 );
2729 }
2730 }
2731
2732 #[test]
2735 fn cross_language_all_six_produce_symbols() {
2736 let provider = TreeSitterProvider::new();
2737
2738 let fixtures = [
2739 ("sample.ts", "TypeScript"),
2740 ("sample.tsx", "TSX"),
2741 ("sample.js", "JavaScript"),
2742 ("sample.py", "Python"),
2743 ("sample.rs", "Rust"),
2744 ("sample.go", "Go"),
2745 ];
2746
2747 for (fixture, lang) in &fixtures {
2748 let symbols = provider
2749 .list_symbols(&fixture_path(fixture))
2750 .unwrap_or_else(|e| panic!("{} ({}) failed: {:?}", lang, fixture, e));
2751 assert!(
2752 symbols.len() >= 2,
2753 "{} should produce ≥2 symbols, got {}: {:?}",
2754 lang,
2755 symbols.len(),
2756 symbols.iter().map(|s| &s.name).collect::<Vec<_>>()
2757 );
2758 }
2759 }
2760}