vtcode_core/tools/tree_sitter/
analyzer.rs

1//! Core tree-sitter analyzer for code parsing and analysis
2
3use crate::tools::tree_sitter::analysis::{
4    CodeAnalysis, CodeMetrics, DependencyInfo, DependencyKind,
5};
6use crate::tools::tree_sitter::languages::*;
7use anyhow::Result;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10// Swift parser is currently disabled to avoid optional dependency issues
11// use tree_sitter_swift;
12use std::path::Path;
13use tree_sitter::{Language, Parser, Tree};
14
15/// Tree-sitter analysis error
16#[derive(Debug, thiserror::Error)]
17pub enum TreeSitterError {
18    #[error("Unsupported language: {0}")]
19    UnsupportedLanguage(String),
20
21    #[error("Parse error: {0}")]
22    ParseError(String),
23
24    #[error("File read error: {0}")]
25    FileReadError(String),
26
27    #[error("Language detection failed: {0}")]
28    LanguageDetectionError(String),
29
30    #[error("Query execution error: {0}")]
31    QueryError(String),
32}
33
34/// Language support enumeration
35#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, Hash, PartialEq)]
36pub enum LanguageSupport {
37    Rust,
38    Python,
39    JavaScript,
40    TypeScript,
41    Go,
42    Java,
43    Swift,
44}
45
46/// Syntax tree representation
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct SyntaxTree {
49    pub root: SyntaxNode,
50    pub source_code: String,
51    pub language: LanguageSupport,
52    pub diagnostics: Vec<Diagnostic>,
53}
54
55/// Syntax node in the tree
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct SyntaxNode {
58    pub kind: String,
59    pub start_position: Position,
60    pub end_position: Position,
61    pub text: String,
62    // Children within the AST subtree
63    pub children: Vec<SyntaxNode>,
64    pub named_children: HashMap<String, Vec<SyntaxNode>>,
65    // Collected comments that immediately precede this node as sibling comments
66    // (useful for documentation extraction like docstrings or /// comments)
67    pub leading_comments: Vec<String>,
68}
69
70/// Position in source code
71#[derive(Debug, Clone, Serialize, Deserialize, Eq, Hash, PartialEq)]
72pub struct Position {
73    pub row: usize,
74    pub column: usize,
75    pub byte_offset: usize,
76}
77
78/// Diagnostic information
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct Diagnostic {
81    pub level: DiagnosticLevel,
82    pub message: String,
83    pub position: Position,
84    pub node_kind: String,
85}
86
87#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
88pub enum DiagnosticLevel {
89    Error,
90    Warning,
91    Info,
92}
93
94/// Main tree-sitter analyzer
95pub struct TreeSitterAnalyzer {
96    parsers: HashMap<LanguageSupport, Parser>,
97    supported_languages: Vec<LanguageSupport>,
98    current_file: String,
99}
100
101impl TreeSitterAnalyzer {
102    /// Create a new tree-sitter analyzer
103    pub fn new() -> Result<Self> {
104        let mut parsers = HashMap::new();
105
106        // Initialize parsers for all supported languages
107        let languages = vec![
108            LanguageSupport::Rust,
109            LanguageSupport::Python,
110            LanguageSupport::JavaScript,
111            LanguageSupport::TypeScript,
112            LanguageSupport::Go,
113            LanguageSupport::Java,
114        ];
115
116        for language in &languages {
117            let mut parser = Parser::new();
118            let ts_language = get_language(language.clone())?;
119            parser.set_language(&ts_language)?;
120            parsers.insert(language.clone(), parser);
121        }
122
123        Ok(Self {
124            parsers,
125            supported_languages: languages,
126            current_file: String::new(),
127        })
128    }
129
130    /// Get supported languages
131    pub fn supported_languages(&self) -> &[LanguageSupport] {
132        &self.supported_languages
133    }
134
135    /// Detect language from file extension
136    pub fn detect_language_from_path<P: AsRef<Path>>(&self, path: P) -> Result<LanguageSupport> {
137        let path = path.as_ref();
138        let extension = path
139            .extension()
140            .and_then(|ext| ext.to_str())
141            .ok_or_else(|| {
142                TreeSitterError::LanguageDetectionError("No file extension found".to_string())
143            })?;
144
145        match extension {
146            "rs" => Ok(LanguageSupport::Rust),
147            "py" => Ok(LanguageSupport::Python),
148            "js" => Ok(LanguageSupport::JavaScript),
149            "ts" => Ok(LanguageSupport::TypeScript),
150            "tsx" => Ok(LanguageSupport::TypeScript),
151            "jsx" => Ok(LanguageSupport::JavaScript),
152            "go" => Ok(LanguageSupport::Go),
153            "java" => Ok(LanguageSupport::Java),
154            "swift" => Ok(LanguageSupport::Swift),
155            _ => Err(TreeSitterError::UnsupportedLanguage(extension.to_string()).into()),
156        }
157    }
158
159    /// Parse source code into a syntax tree
160    pub fn parse(&mut self, source_code: &str, language: LanguageSupport) -> Result<Tree> {
161        let parser = self
162            .parsers
163            .get_mut(&language)
164            .ok_or_else(|| TreeSitterError::UnsupportedLanguage(format!("{:?}", language)))?;
165
166        let tree = parser.parse(source_code, None).ok_or_else(|| {
167            TreeSitterError::ParseError("Failed to parse source code".to_string())
168        })?;
169
170        Ok(tree)
171    }
172
173    /// Extract symbols from a syntax tree
174    pub fn extract_symbols(
175        &mut self,
176        syntax_tree: &Tree,
177        source_code: &str,
178        language: LanguageSupport,
179    ) -> Result<Vec<SymbolInfo>> {
180        let mut symbols = Vec::new();
181        let root_node = syntax_tree.root_node();
182
183        // Walk the tree and extract symbols based on language
184        self.extract_symbols_recursive(root_node, source_code, language, &mut symbols, None)?;
185
186        Ok(symbols)
187    }
188
189    /// Recursively extract symbols from a node
190    fn extract_symbols_recursive(
191        &self,
192        node: tree_sitter::Node,
193        source_code: &str,
194        language: LanguageSupport,
195        symbols: &mut Vec<SymbolInfo>,
196        parent_scope: Option<String>,
197    ) -> Result<()> {
198        let _node_text = &source_code[node.start_byte()..node.end_byte()];
199        let kind = node.kind();
200
201        // Extract symbols based on node type and language
202        match language {
203            LanguageSupport::Rust => {
204                if kind == "function_item" || kind == "method_definition" {
205                    // Extract function name
206                    if let Some(name_node) = self.find_child_by_type(node, "identifier") {
207                        let name = &source_code[name_node.start_byte()..name_node.end_byte()];
208                        symbols.push(SymbolInfo {
209                            name: name.to_string(),
210                            kind: SymbolKind::Function,
211                            position: Position {
212                                row: node.start_position().row,
213                                column: node.start_position().column,
214                                byte_offset: node.start_byte(),
215                            },
216                            scope: parent_scope.clone(),
217                            signature: None,
218                            documentation: None,
219                        });
220                    }
221                } else if kind == "struct_item" || kind == "enum_item" {
222                    // Extract type name
223                    if let Some(name_node) = self.find_child_by_type(node, "type_identifier") {
224                        let name = &source_code[name_node.start_byte()..name_node.end_byte()];
225                        symbols.push(SymbolInfo {
226                            name: name.to_string(),
227                            kind: SymbolKind::Type,
228                            position: Position {
229                                row: node.start_position().row,
230                                column: node.start_position().column,
231                                byte_offset: node.start_byte(),
232                            },
233                            scope: parent_scope.clone(),
234                            signature: None,
235                            documentation: None,
236                        });
237                    }
238                }
239            }
240            LanguageSupport::Python => {
241                if kind == "function_definition" {
242                    // Extract function name
243                    if let Some(name_node) = self.find_child_by_type(node, "identifier") {
244                        let name = &source_code[name_node.start_byte()..name_node.end_byte()];
245                        symbols.push(SymbolInfo {
246                            name: name.to_string(),
247                            kind: SymbolKind::Function,
248                            position: Position {
249                                row: node.start_position().row,
250                                column: node.start_position().column,
251                                byte_offset: node.start_byte(),
252                            },
253                            scope: parent_scope.clone(),
254                            signature: None,
255                            documentation: None,
256                        });
257                    }
258                } else if kind == "class_definition" {
259                    // Extract class name
260                    if let Some(name_node) = self.find_child_by_type(node, "identifier") {
261                        let name = &source_code[name_node.start_byte()..name_node.end_byte()];
262                        symbols.push(SymbolInfo {
263                            name: name.to_string(),
264                            kind: SymbolKind::Type,
265                            position: Position {
266                                row: node.start_position().row,
267                                column: node.start_position().column,
268                                byte_offset: node.start_byte(),
269                            },
270                            scope: parent_scope.clone(),
271                            signature: None,
272                            documentation: None,
273                        });
274                    }
275                }
276            }
277            _ => {
278                // For other languages, do a basic extraction
279                if kind.contains("function") || kind.contains("method") {
280                    // Try to find a name
281                    if let Some(name_node) = self.find_child_by_type(node, "identifier") {
282                        let name = &source_code[name_node.start_byte()..name_node.end_byte()];
283                        symbols.push(SymbolInfo {
284                            name: name.to_string(),
285                            kind: SymbolKind::Function,
286                            position: Position {
287                                row: node.start_position().row,
288                                column: node.start_position().column,
289                                byte_offset: node.start_byte(),
290                            },
291                            scope: parent_scope.clone(),
292                            signature: None,
293                            documentation: None,
294                        });
295                    }
296                }
297            }
298        }
299
300        // Recursively process children
301        let mut cursor = node.walk();
302        for child in node.children(&mut cursor) {
303            self.extract_symbols_recursive(
304                child,
305                source_code,
306                language.clone(),
307                symbols,
308                parent_scope.clone(),
309            )?;
310        }
311
312        Ok(())
313    }
314
315    /// Find a child node of a specific type
316    fn find_child_by_type<'a>(
317        &self,
318        node: tree_sitter::Node<'a>,
319        type_name: &str,
320    ) -> Option<tree_sitter::Node<'a>> {
321        let mut cursor = node.walk();
322        for child in node.children(&mut cursor) {
323            if child.kind() == type_name {
324                return Some(child);
325            }
326        }
327        None
328    }
329
330    /// Extract dependencies from a syntax tree
331    pub fn extract_dependencies(
332        &self,
333        syntax_tree: &Tree,
334        language: LanguageSupport,
335    ) -> Result<Vec<DependencyInfo>> {
336        let mut dependencies = Vec::new();
337        let root_node = syntax_tree.root_node();
338
339        // Extract dependencies based on language
340        match language {
341            LanguageSupport::Rust => {
342                self.extract_rust_dependencies(root_node, &mut dependencies)?;
343            }
344            LanguageSupport::Python => {
345                self.extract_python_dependencies(root_node, &mut dependencies)?;
346            }
347            LanguageSupport::JavaScript | LanguageSupport::TypeScript => {
348                self.extract_js_dependencies(root_node, &mut dependencies)?;
349            }
350            _ => {
351                // For other languages, do a basic extraction
352                self.extract_basic_dependencies(root_node, &mut dependencies)?;
353            }
354        }
355
356        Ok(dependencies)
357    }
358
359    /// Extract Rust dependencies
360    fn extract_rust_dependencies(
361        &self,
362        node: tree_sitter::Node,
363        dependencies: &mut Vec<DependencyInfo>,
364    ) -> Result<()> {
365        let mut cursor = node.walk();
366
367        // Look for use statements and extern crate declarations
368        if node.kind() == "use_declaration" {
369            // Extract the path from the use statement
370            if let Some(_path_node) = self
371                .find_child_by_type(node, "use_list")
372                .or_else(|| self.find_child_by_type(node, "scoped_identifier"))
373                .or_else(|| self.find_child_by_type(node, "identifier"))
374            {
375                // This is a simplified extraction
376                dependencies.push(DependencyInfo {
377                    name: "unknown_rust_dep".to_string(), // Would need more parsing for actual name
378                    kind: DependencyKind::Import,
379                    source: "use_declaration".to_string(),
380                    position: Position {
381                        row: node.start_position().row,
382                        column: node.start_position().column,
383                        byte_offset: node.start_byte(),
384                    },
385                });
386            }
387        } else if node.kind() == "extern_crate_declaration" {
388            // Extract crate name from extern crate declaration
389            if let Some(_name_node) = self.find_child_by_type(node, "identifier") {
390                dependencies.push(DependencyInfo {
391                    name: "unknown_crate".to_string(), // Would need more parsing for actual name
392                    kind: DependencyKind::External,
393                    source: "extern_crate".to_string(),
394                    position: Position {
395                        row: node.start_position().row,
396                        column: node.start_position().column,
397                        byte_offset: node.start_byte(),
398                    },
399                });
400            }
401        }
402
403        // Recursively process children
404        for child in node.children(&mut cursor) {
405            self.extract_rust_dependencies(child, dependencies)?;
406        }
407
408        Ok(())
409    }
410
411    /// Extract Python dependencies
412    fn extract_python_dependencies(
413        &self,
414        node: tree_sitter::Node,
415        dependencies: &mut Vec<DependencyInfo>,
416    ) -> Result<()> {
417        let mut cursor = node.walk();
418
419        // Look for import statements
420        if node.kind() == "import_statement" || node.kind() == "import_from_statement" {
421            // Extract the module name
422            dependencies.push(DependencyInfo {
423                name: "unknown_python_module".to_string(), // Would need more parsing for actual name
424                kind: DependencyKind::Import,
425                source: node.kind().to_string(),
426                position: Position {
427                    row: node.start_position().row,
428                    column: node.start_position().column,
429                    byte_offset: node.start_byte(),
430                },
431            });
432        }
433
434        // Recursively process children
435        for child in node.children(&mut cursor) {
436            self.extract_python_dependencies(child, dependencies)?;
437        }
438
439        Ok(())
440    }
441
442    /// Extract JavaScript/TypeScript dependencies
443    fn extract_js_dependencies(
444        &self,
445        node: tree_sitter::Node,
446        dependencies: &mut Vec<DependencyInfo>,
447    ) -> Result<()> {
448        let mut cursor = node.walk();
449
450        // Look for import statements
451        if node.kind() == "import_statement" {
452            // Extract the module name
453            dependencies.push(DependencyInfo {
454                name: "unknown_js_module".to_string(), // Would need more parsing for actual name
455                kind: DependencyKind::Import,
456                source: node.kind().to_string(),
457                position: Position {
458                    row: node.start_position().row,
459                    column: node.start_position().column,
460                    byte_offset: node.start_byte(),
461                },
462            });
463        }
464
465        // Recursively process children
466        for child in node.children(&mut cursor) {
467            self.extract_js_dependencies(child, dependencies)?;
468        }
469
470        Ok(())
471    }
472
473    /// Extract basic dependencies (fallback)
474    fn extract_basic_dependencies(
475        &self,
476        node: tree_sitter::Node,
477        dependencies: &mut Vec<DependencyInfo>,
478    ) -> Result<()> {
479        let mut cursor = node.walk();
480
481        // Look for import/include statements
482        if node.kind().contains("import") || node.kind().contains("include") {
483            // Extract the dependency name
484            dependencies.push(DependencyInfo {
485                name: "unknown_dependency".to_string(),
486                kind: DependencyKind::Import,
487                source: node.kind().to_string(),
488                position: Position {
489                    row: node.start_position().row,
490                    column: node.start_position().column,
491                    byte_offset: node.start_byte(),
492                },
493            });
494        }
495
496        // Recursively process children
497        for child in node.children(&mut cursor) {
498            self.extract_basic_dependencies(child, dependencies)?;
499        }
500
501        Ok(())
502    }
503
504    /// Calculate code metrics from a syntax tree
505    pub fn calculate_metrics(&self, syntax_tree: &Tree, source_code: &str) -> Result<CodeMetrics> {
506        let root_node = syntax_tree.root_node();
507        let lines = source_code.lines().collect::<Vec<_>>();
508
509        // Count different types of nodes
510        let mut functions_count = 0;
511        let mut classes_count = 0;
512        let mut variables_count = 0;
513        let mut imports_count = 0;
514
515        self.count_nodes_recursive(
516            root_node,
517            &mut functions_count,
518            &mut classes_count,
519            &mut variables_count,
520            &mut imports_count,
521        );
522
523        // Count comments
524        let lines_of_comments = lines
525            .iter()
526            .filter(|l| {
527                l.trim().starts_with("//")
528                    || l.trim().starts_with("/*")
529                    || l.trim().starts_with("#")
530            })
531            .count();
532
533        let blank_lines = lines.iter().filter(|l| l.trim().is_empty()).count();
534        let lines_of_code = lines.len();
535
536        let comment_ratio = if lines_of_code > 0 {
537            lines_of_comments as f64 / lines_of_code as f64
538        } else {
539            0.0
540        };
541
542        Ok(CodeMetrics {
543            lines_of_code,
544            lines_of_comments,
545            blank_lines,
546            functions_count,
547            classes_count,
548            variables_count,
549            imports_count,
550            comment_ratio,
551        })
552    }
553
554    /// Recursively count different types of nodes
555    fn count_nodes_recursive(
556        &self,
557        node: tree_sitter::Node,
558        functions_count: &mut usize,
559        classes_count: &mut usize,
560        variables_count: &mut usize,
561        imports_count: &mut usize,
562    ) {
563        let kind = node.kind();
564
565        // Count based on node type
566        if kind.contains("function") || kind.contains("method") {
567            *functions_count += 1;
568        } else if kind.contains("class") || kind.contains("struct") || kind.contains("enum") {
569            *classes_count += 1;
570        } else if kind.contains("variable") || kind.contains("let") || kind.contains("const") {
571            *variables_count += 1;
572        } else if kind.contains("import") || kind.contains("include") || kind.contains("use") {
573            *imports_count += 1;
574        }
575
576        // Recursively process children
577        let mut cursor = node.walk();
578        for child in node.children(&mut cursor) {
579            self.count_nodes_recursive(
580                child,
581                functions_count,
582                classes_count,
583                variables_count,
584                imports_count,
585            );
586        }
587    }
588
589    /// Parse file into a syntax tree
590    pub fn parse_file<P: AsRef<Path>>(&mut self, file_path: P) -> Result<SyntaxTree> {
591        let file_path = file_path.as_ref();
592        let language = self.detect_language_from_path(file_path)?;
593
594        let source_code = std::fs::read_to_string(file_path)
595            .map_err(|e| TreeSitterError::FileReadError(e.to_string()))?;
596
597        let tree = self.parse(&source_code, language.clone())?;
598
599        // Convert tree-sitter tree to our SyntaxTree representation
600        let root = self.convert_tree_to_syntax_node(tree.root_node(), &source_code);
601        let diagnostics = self.collect_diagnostics(&tree, &source_code);
602
603        Ok(SyntaxTree {
604            root,
605            source_code,
606            language,
607            diagnostics,
608        })
609    }
610
611    /// Convert tree-sitter node to our SyntaxNode
612    pub fn convert_tree_to_syntax_node(
613        &self,
614        node: tree_sitter::Node,
615        source_code: &str,
616    ) -> SyntaxNode {
617        let start = node.start_position();
618        let end = node.end_position();
619
620        // First, convert all children sequentially so we can compute leading sibling comments
621        let mut converted_children: Vec<SyntaxNode> = Vec::new();
622        let mut cursor = node.walk();
623        for child in node.children(&mut cursor) {
624            // Gather trailing run of comment siblings immediately preceding this child
625            let mut leading_comments: Vec<String> = Vec::new();
626            for prev in converted_children.iter().rev() {
627                let k = prev.kind.to_lowercase();
628                if k.contains("comment") {
629                    leading_comments.push(prev.text.trim().to_string());
630                } else {
631                    break;
632                }
633            }
634            leading_comments.reverse();
635
636            // Convert current child
637            let mut converted = self.convert_tree_to_syntax_node(child, source_code);
638            converted.leading_comments = leading_comments;
639            converted_children.push(converted);
640        }
641
642        SyntaxNode {
643            kind: node.kind().to_string(),
644            start_position: Position {
645                row: start.row,
646                column: start.column,
647                byte_offset: node.start_byte(),
648            },
649            end_position: Position {
650                row: end.row,
651                column: end.column,
652                byte_offset: node.end_byte(),
653            },
654            text: source_code[node.start_byte()..node.end_byte()].to_string(),
655            children: converted_children,
656            named_children: self.collect_named_children(node, source_code),
657            leading_comments: Vec::new(),
658        }
659    }
660
661    /// Collect named children for easier access
662    fn collect_named_children(
663        &self,
664        node: tree_sitter::Node,
665        source_code: &str,
666    ) -> HashMap<String, Vec<SyntaxNode>> {
667        let mut named_children = HashMap::new();
668
669        for child in node.named_children(&mut node.walk()) {
670            let kind = child.kind().to_string();
671            let syntax_node = self.convert_tree_to_syntax_node(child, source_code);
672
673            named_children
674                .entry(kind)
675                .or_insert_with(Vec::new)
676                .push(syntax_node);
677        }
678
679        named_children
680    }
681
682    /// Collect diagnostics from the parsed tree
683    pub fn collect_diagnostics(&self, tree: &Tree, _source_code: &str) -> Vec<Diagnostic> {
684        let mut diagnostics = Vec::new();
685
686        // Basic diagnostics collection - can be extended with more sophisticated analysis
687        if tree.root_node().has_error() {
688            diagnostics.push(Diagnostic {
689                level: DiagnosticLevel::Error,
690                message: "Syntax error detected in code".to_string(),
691                position: Position {
692                    row: 0,
693                    column: 0,
694                    byte_offset: 0,
695                },
696                node_kind: "root".to_string(),
697            });
698        }
699
700        diagnostics
701    }
702
703    /// Get parser statistics
704    pub fn get_parser_stats(&self) -> HashMap<String, usize> {
705        let mut stats = HashMap::new();
706        stats.insert(
707            "supported_languages".to_string(),
708            self.supported_languages.len(),
709        );
710        stats
711    }
712
713    pub fn analyze_file_with_tree_sitter(
714        &mut self,
715        file_path: &std::path::Path,
716        source_code: &str,
717    ) -> Result<CodeAnalysis> {
718        let language = self
719            .detect_language_from_path(file_path)
720            .unwrap_or_else(|_| {
721                self.detect_language_from_content(source_code)
722                    .unwrap_or(LanguageSupport::Rust)
723            });
724
725        self.current_file = file_path.to_string_lossy().to_string();
726
727        let tree = self.parse(source_code, language.clone())?;
728
729        // Extract actual symbols and dependencies
730        let symbols = self.extract_symbols(&tree, source_code, language.clone())?;
731        let dependencies = self.extract_dependencies(&tree, language.clone())?;
732        let metrics = self.calculate_metrics(&tree, source_code)?;
733
734        Ok(CodeAnalysis {
735            file_path: self.current_file.clone(),
736            language,
737            symbols,
738            dependencies,
739            metrics,
740            issues: vec![], // Would need to implement actual issue detection
741            complexity: Default::default(), // Would need to implement actual complexity analysis
742            structure: Default::default(), // Would need to implement actual structure analysis
743        })
744    }
745}
746
747/// Helper function to get tree-sitter language
748fn get_language(language: LanguageSupport) -> Result<Language> {
749    let lang = match language {
750        LanguageSupport::Rust => tree_sitter_rust::LANGUAGE,
751        LanguageSupport::Python => tree_sitter_python::LANGUAGE,
752        LanguageSupport::JavaScript => tree_sitter_javascript::LANGUAGE,
753        LanguageSupport::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT,
754        LanguageSupport::Go => tree_sitter_go::LANGUAGE,
755        LanguageSupport::Java => tree_sitter_java::LANGUAGE,
756        LanguageSupport::Swift => {
757            #[cfg(feature = "swift")]
758            {
759                tree_sitter_swift::LANGUAGE
760            }
761            #[cfg(not(feature = "swift"))]
762            {
763                return Err(TreeSitterError::UnsupportedLanguage("Swift".to_string()).into());
764            }
765        }
766    };
767    Ok(lang.into())
768}
769
770impl std::fmt::Display for LanguageSupport {
771    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
772        let language_name = match self {
773            LanguageSupport::Rust => "Rust",
774            LanguageSupport::Python => "Python",
775            LanguageSupport::JavaScript => "JavaScript",
776            LanguageSupport::TypeScript => "TypeScript",
777            LanguageSupport::Go => "Go",
778            LanguageSupport::Java => "Java",
779            LanguageSupport::Swift => "Swift",
780        };
781        write!(f, "{}", language_name)
782    }
783}
784
785impl TreeSitterAnalyzer {
786    pub fn detect_language_from_content(&self, content: &str) -> Option<LanguageSupport> {
787        // Simple heuristic-based language detection
788        if content.contains("fn ") && content.contains("{") && content.contains("}") {
789            Some(LanguageSupport::Rust)
790        } else if content.contains("def ") && content.contains(":") && !content.contains("{") {
791            Some(LanguageSupport::Python)
792        } else if content.contains("function") && content.contains("{") && content.contains("}") {
793            Some(LanguageSupport::JavaScript)
794        } else {
795            None
796        }
797    }
798}
799
800#[cfg(test)]
801mod tests {
802    use super::*;
803    use std::path::Path;
804
805    fn create_test_analyzer() -> TreeSitterAnalyzer {
806        TreeSitterAnalyzer::new().expect("Failed to create analyzer")
807    }
808
809    #[test]
810    fn test_analyzer_creation() {
811        let analyzer = create_test_analyzer();
812        assert!(
813            analyzer
814                .supported_languages
815                .contains(&LanguageSupport::Rust)
816        );
817        assert!(
818            analyzer
819                .supported_languages
820                .contains(&LanguageSupport::Python)
821        );
822    }
823
824    #[test]
825    fn test_language_detection_from_path() {
826        let analyzer = create_test_analyzer();
827
828        // Test basic file extensions
829        match analyzer.detect_language_from_path(Path::new("main.rs")) {
830            Ok(lang) => assert_eq!(lang, LanguageSupport::Rust),
831            Err(e) => panic!("Expected Rust language, got error: {}", e),
832        }
833
834        match analyzer.detect_language_from_path(Path::new("script.py")) {
835            Ok(lang) => assert_eq!(lang, LanguageSupport::Python),
836            Err(e) => panic!("Expected Python language, got error: {}", e),
837        }
838
839        // Test unknown extension should return error
840        assert!(
841            analyzer
842                .detect_language_from_path(Path::new("file.unknown"))
843                .is_err()
844        );
845    }
846
847    #[test]
848    fn test_language_detection_from_content() {
849        let analyzer = create_test_analyzer();
850
851        // Test Rust content
852        let rust_code = r#"fn main() { println!("Hello, world!"); let x = 42; }"#;
853        assert_eq!(
854            analyzer.detect_language_from_content(rust_code),
855            Some(LanguageSupport::Rust)
856        );
857
858        // Test Python content
859        let python_code = r#"def main(): print("Hello, world!"); x = 42"#;
860        assert_eq!(
861            analyzer.detect_language_from_content(python_code),
862            Some(LanguageSupport::Python)
863        );
864
865        // Test unknown content
866        let unknown_code = "This is not code just plain text.";
867        assert_eq!(analyzer.detect_language_from_content(unknown_code), None);
868    }
869
870    #[test]
871    fn test_parse_rust_code() {
872        let mut analyzer = create_test_analyzer();
873
874        let rust_code = r#"fn main() { println!("Hello, world!"); let x = 42; }"#;
875
876        let result = analyzer.parse(rust_code, LanguageSupport::Rust);
877        assert!(result.is_ok());
878
879        let tree = result.unwrap();
880        assert!(!tree.root_node().has_error());
881    }
882
883    #[cfg(feature = "swift")]
884    #[test]
885    fn test_parse_swift_code() {
886        let mut analyzer = create_test_analyzer();
887        let swift_code = r#"print(\"Hello, World!\")"#;
888        let result = analyzer.parse(swift_code, LanguageSupport::Swift);
889        assert!(result.is_ok());
890        let tree = result.unwrap();
891        assert!(!tree.root_node().has_error());
892    }
893}