Skip to main content

aft/
parser.rs

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
15// --- Query patterns embedded at compile time ---
16
17const 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/// Supported language identifier.
146#[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
157/// Maps file extension to language identifier.
158pub 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
172/// Returns the tree-sitter Language grammar for a given LangId.
173pub 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
185/// Returns the query pattern string for a given LangId, if implemented.
186fn 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
197/// Cached parse result: mtime at parse time + the tree.
198struct CachedTree {
199    mtime: SystemTime,
200    tree: Tree,
201}
202
203/// Core parsing engine. Handles language detection, parse tree caching,
204/// and query pattern execution via tree-sitter.
205pub struct FileParser {
206    cache: HashMap<PathBuf, CachedTree>,
207}
208
209impl FileParser {
210    /// Create a new `FileParser` with an empty parse cache.
211    pub fn new() -> Self {
212        Self {
213            cache: HashMap::new(),
214        }
215    }
216
217    /// Parse a file, returning the tree and detected language. Uses cache if
218    /// the file hasn't been modified since last parse.
219    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        // Check cache validity
237        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    /// Like [`FileParser::parse`] but returns an owned `Tree` clone.
279    ///
280    /// Useful when the caller needs to hold the tree while also calling
281    /// other mutable methods on this parser.
282    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    /// Extract symbols from a file using language-specific query patterns.
288    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        // Markdown uses direct tree walking, not query patterns
297        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
324/// Build a Range from a tree-sitter Node.
325pub(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
336/// Build a Range from a tree-sitter Node, expanding upward to include
337/// preceding attributes, decorators, and doc comments that belong to the symbol.
338///
339/// This ensures that when agents edit/replace a symbol, they get the full
340/// declaration including `#[test]`, `#[derive(...)]`, `/// doc`, `@decorator`, etc.
341pub(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                // Include #[...] attributes
350                kind == "attribute_item"
351                    // Include /// doc comments (but not regular // comments)
352                    || (kind == "line_comment"
353                        && node_text(source, &prev).starts_with("///"))
354                    // Include /** ... */ doc comments
355                    || (kind == "block_comment"
356                        && node_text(source, &prev).starts_with("/**"))
357            }
358            LangId::TypeScript | LangId::Tsx | LangId::JavaScript => {
359                // Include @decorator
360                kind == "decorator"
361                    // Include /** JSDoc */ comments
362                    || (kind == "comment"
363                        && node_text(source, &prev).starts_with("/**"))
364            }
365            LangId::Go => {
366                // Include doc comments only if immediately above (no blank line gap)
367                kind == "comment" && is_adjacent_line(&prev, &current, source)
368            }
369            LangId::Python => {
370                // Decorators are handled by decorated_definition capture
371                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
388/// Check if two nodes are on adjacent lines (no blank line between them).
389fn 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    // Check that there's no blank line between them
398    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
407/// Extract the text of a node from source.
408pub(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
439/// Collect byte ranges of all export_statement nodes from query matches.
440fn 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
467/// Check if a node's byte range is contained within any export statement.
468fn 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
475/// Extract the first line of a node as its signature.
476fn 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    // Trim trailing opening brace if present
480    let trimmed = first_line.trim_end();
481    let trimmed = trimmed.strip_suffix('{').unwrap_or(trimmed).trim_end();
482    trimmed.to_string()
483}
484
485/// Extract symbols from TypeScript / TSX source.
486fn 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        // Determine what kind of match this is by looking at capture names
501        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                // var.value/var.decl removed — not needed
540                _ => {}
541            }
542        }
543
544        // Function declaration
545        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        // Arrow function
558        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        // Class declaration
571        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        // Method definition
584        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, // methods inherit export from class
595                parent: Some(class_name),
596            });
597        }
598
599        // Interface declaration
600        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        // Enum declaration
613        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        // Type alias
626        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        // Top-level const/let variable declaration (not arrow functions — those are handled above)
639        if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
640            // Only include module-scope variables (parent is program/export_statement, not inside a function)
641            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    // Deduplicate: methods can appear as both class and method captures
663    dedup_symbols(&mut symbols);
664    Ok(symbols)
665}
666
667/// Extract symbols from JavaScript source.
668fn 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
764/// Walk parent nodes to build a scope chain for Python symbols.
765/// A function inside `class_definition > block` gets the class name in its scope.
766fn 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
781/// Extract symbols from Python source.
782fn 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    // Track decorated definitions to avoid double-counting
791    let mut decorated_fn_lines = std::collections::HashSet::new();
792
793    // First pass: collect decorated definition info
794    {
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                // Find the inner function_definition or class_definition
815                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        // Function definition
854        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            // Skip __init__ and other dunders as separate symbols — they're methods
859            let kind = if is_method {
860                SymbolKind::Method
861            } else {
862                SymbolKind::Function
863            };
864
865            // Build signature — include decorator if this is a decorated function
866            let sig = if decorated_fn_lines.contains(&def_node.start_position().row) {
867                // Find the decorated_definition parent to get decorator text
868                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                        // Get decorator lines
873                        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, // Python has no export concept
901                parent: scope.last().cloned(),
902            });
903        }
904
905        // Class definition
906        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            // Build signature — include decorator if decorated
910            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
952/// Extract symbols from Rust source.
953/// Handles: free functions, struct, enum, trait (as Interface), impl methods with scope chains.
954fn 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    // Collect all visibility_modifier byte ranges first
959    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        // Check if the node has a visibility_modifier as a direct child
981        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        // Free function (not inside impl block — check parent)
1030        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        // Struct
1049        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        // Enum
1062        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        // Trait (mapped to Interface kind)
1075        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        // Impl block — extract methods from inside
1088        if let Some(impl_node) = impl_def_node {
1089            // Find the type name(s) from the impl
1090            // `impl TypeName { ... }` → scope = ["TypeName"]
1091            // `impl Trait for TypeName { ... }` → scope = ["Trait for TypeName"]
1092            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                // impl Trait for Type
1108                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            // Find declaration_list and extract function_items
1118            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
1168/// Extract symbols from Go source.
1169/// Handles: functions, methods (with receiver scope chain), struct/interface types,
1170/// uppercase-first-letter export detection.
1171fn 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        // Function declaration
1213        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        // Method declaration (with receiver)
1227        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            // Extract receiver type from the first parameter_list
1231            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        // Type declarations (struct or interface)
1250        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
1276/// Extract the receiver type from a Go method_declaration node.
1277/// e.g. `func (m *MyStruct) String()` → Some("MyStruct")
1278fn extract_go_receiver_type(method_node: &Node, source: &str) -> Option<String> {
1279    // The first parameter_list is the receiver
1280    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                // Walk into parameter_list to find type_identifier
1286                return find_type_identifier_recursive(&child, source);
1287            }
1288            if !child_cursor.goto_next_sibling() {
1289                break;
1290            }
1291        }
1292    }
1293    None
1294}
1295
1296/// Recursively find the first type_identifier node in a subtree.
1297fn 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
1315/// Extract markdown headings as symbols.
1316/// Each heading becomes a symbol with kind `Heading`, and its range covers the entire
1317/// section (from the heading to the next heading at the same or higher level, or EOF).
1318fn 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
1324/// Recursively walk `section` nodes to build the heading hierarchy.
1325fn 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                // A section contains an atx_heading as its first child,
1341                // followed by content and possibly nested sections.
1342                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                            // Extract heading level from marker type
1351                            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                                        // "atx_h1_marker" → level 1, "atx_h2_marker" → level 2, etc.
1358                                        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                    // Recurse into the section for nested headings
1395                    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
1409/// Remove duplicate symbols based on (name, kind, start_line).
1410/// Class declarations can match both "class" and "method" patterns,
1411/// producing duplicates.
1412fn 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
1420/// Provider that uses tree-sitter for real symbol extraction.
1421/// Implements the `LanguageProvider` trait from `language.rs`.
1422pub 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    /// Create a new `TreeSitterProvider` backed by a fresh `FileParser`.
1434    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    // --- Language detection ---
1857
1858    #[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    // --- Unsupported extension error ---
1908
1909    #[test]
1910    fn unsupported_extension_returns_invalid_request() {
1911        // Use a file that exists but has an unsupported extension
1912        let path = fixture_path("sample.ts");
1913        let bad_path = path.with_extension("txt");
1914        // Create a dummy file so the error comes from language detection, not I/O
1915        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    // --- TypeScript extraction ---
1933
1934    #[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        // At least 6 unique symbols as required
1973        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); // arrow fn → Function
1990        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    // --- JavaScript extraction ---
2070
2071    #[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    // --- TSX extraction ---
2141
2142    #[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        // Main assertion: TSX grammar handles JSX without errors
2175        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    // --- resolve_symbol ---
2185
2186    #[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    // --- Parse tree caching ---
2269
2270    #[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        // Same tree (cache hit) should return identical root node range
2342        assert_eq!(tree1_root, tree2_root);
2343    }
2344
2345    // --- Python extraction ---
2346
2347    #[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        // Plan requires ≥4 symbols
2371        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        // Method inside MyClass
2404        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        // Method inside OuterClass > InnerClass
2412        assert_eq!(
2413            find("inner_method").scope_chain,
2414            vec!["OuterClass", "InnerClass"],
2415            "inner_method should have nested scope chain"
2416        );
2417
2418        // InnerClass itself should have OuterClass in scope
2419        assert_eq!(
2420            find("InnerClass").scope_chain,
2421            vec!["OuterClass"],
2422            "InnerClass should have OuterClass in scope"
2423        );
2424
2425        // Top-level function has empty scope
2426        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    // --- Rust extraction ---
2468
2469    #[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        // impl methods
2493        assert!(
2494            names.contains(&"new"),
2495            "missing impl method new: {:?}",
2496            names
2497        );
2498
2499        // Plan requires ≥6 symbols
2500        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); // trait → Interface
2520        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        // `impl MyStruct { fn new() }` → scope chain = ["MyStruct"]
2559        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        // Free function has empty scope chain
2567        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        // `impl Drawable for MyStruct { fn draw() }` → scope = ["Drawable for MyStruct"]
2579        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    // --- Go extraction ---
2604
2605    #[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        // receiver method
2632        assert!(
2633            names.contains(&"String"),
2634            "missing receiver method String: {:?}",
2635            names
2636        );
2637
2638        // Plan requires ≥4 symbols
2639        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        // `func (m *MyStruct) String()` → scope chain = ["MyStruct"]
2703        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        // Regular function has empty scope chain
2711        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    // --- Cross-language ---
2733
2734    #[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}