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