ai_code_buddy/core/
ai_analyzer.rs

1use anyhow::Result;
2use serde::{Deserialize, Serialize};
3use std::path::Path;
4use tokio::sync::mpsc;
5
6use crate::core::review::{CommitStatus, Issue};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct AnalysisRequest {
10    pub file_path: String,
11    pub content: String,
12    pub language: String,
13    pub commit_status: CommitStatus,
14}
15
16#[derive(Debug, Clone)]
17pub struct ProgressUpdate {
18    pub current_file: String,
19    pub progress: f64,
20    pub stage: String,
21}
22
23#[derive(Debug, Clone, PartialEq)]
24pub enum GpuBackend {
25    Metal,
26    Cuda,
27    Mkl,
28    Cpu,
29}
30
31impl std::fmt::Display for GpuBackend {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        match self {
34            GpuBackend::Metal => write!(f, "Metal"),
35            GpuBackend::Cuda => write!(f, "CUDA"),
36            GpuBackend::Mkl => write!(f, "MKL"),
37            GpuBackend::Cpu => write!(f, "CPU"),
38        }
39    }
40}
41
42pub struct AIAnalyzer {
43    backend: GpuBackend,
44    enable_ai: bool,
45}
46
47impl AIAnalyzer {
48    pub async fn new(use_gpu: bool, enable_ai: bool) -> Result<Self> {
49        println!("🧠 Initializing AI analyzer...");
50
51        // Detect and configure GPU backend
52        let backend = if use_gpu {
53            Self::detect_gpu_backend()
54        } else {
55            GpuBackend::Cpu
56        };
57
58        println!("šŸ”§ Using backend: {backend:?}");
59
60        if enable_ai {
61            println!("šŸ¤– AI inference enabled - using advanced AI analysis");
62        } else {
63            println!("ļæ½ AI inference disabled - using rule-based analysis only");
64        }
65
66        let analyzer = AIAnalyzer { backend, enable_ai };
67
68        // Display the configured backend for diagnostics
69        println!(
70            "šŸ”§ AI Analyzer initialized with {} backend",
71            analyzer.get_backend()
72        );
73
74        Ok(analyzer)
75    }
76
77    /// Get the GPU backend being used by this analyzer
78    pub fn get_backend(&self) -> &GpuBackend {
79        &self.backend
80    }
81
82    fn detect_gpu_backend() -> GpuBackend {
83        // Check if we're on Apple Silicon (Metal support)
84        if cfg!(target_os = "macos") && Self::is_apple_silicon() {
85            println!("šŸŽ Apple Silicon detected, using Metal backend");
86            GpuBackend::Metal
87        }
88        // Check for CUDA support (NVIDIA)
89        else if Self::has_cuda_support() {
90            println!("🟢 NVIDIA CUDA detected, using CUDA backend");
91            GpuBackend::Cuda
92        }
93        // Check for Intel MKL support
94        else if Self::has_mkl_support() {
95            println!("šŸ”µ Intel MKL detected, using MKL backend");
96            GpuBackend::Mkl
97        }
98        // Fallback to CPU
99        else {
100            println!("šŸ’» No GPU acceleration detected, falling back to CPU");
101            GpuBackend::Cpu
102        }
103    }
104
105    fn is_apple_silicon() -> bool {
106        // Check if we're running on Apple Silicon
107        cfg!(target_arch = "aarch64") && cfg!(target_os = "macos")
108    }
109
110    fn has_cuda_support() -> bool {
111        // Check for NVIDIA GPU presence
112        // This is a simplified check - in production you might want to check for actual CUDA runtime
113        std::process::Command::new("nvidia-smi")
114            .output()
115            .map(|output| output.status.success())
116            .unwrap_or(false)
117    }
118
119    fn has_mkl_support() -> bool {
120        // Check for Intel processor
121        // This is a simplified check
122        cfg!(target_arch = "x86_64")
123    }
124
125    pub async fn analyze_file(
126        &self,
127        request: AnalysisRequest,
128        progress_tx: Option<mpsc::UnboundedSender<ProgressUpdate>>,
129    ) -> Result<Vec<Issue>> {
130        let _language = self.detect_language(&request.file_path);
131
132        if let Some(ref tx) = progress_tx {
133            let _ = tx.send(ProgressUpdate {
134                current_file: request.file_path.clone(),
135                progress: 0.0,
136                stage: "Starting analysis".to_string(),
137            });
138        }
139
140        let mut issues = Vec::new();
141
142        // Check if AI analysis is enabled
143        if self.enable_ai {
144            println!("šŸ¤– AI inference enabled - using advanced AI analysis");
145            // TODO: Implement actual AI analysis methods here
146            // For now, we'll extend the rule-based analysis with AI-enhanced patterns
147            issues.extend(self.ai_enhanced_analysis(&request)?);
148        } else {
149            println!("šŸ” AI inference disabled - using rule-based analysis only");
150            issues.extend(self.rule_based_analysis(&request)?);
151        }
152
153        if let Some(ref tx) = progress_tx {
154            let _ = tx.send(ProgressUpdate {
155                current_file: request.file_path.clone(),
156                progress: 100.0,
157                stage: "Analysis complete".to_string(),
158            });
159        }
160
161        Ok(issues)
162    }
163
164    pub fn rule_based_analysis(&self, request: &AnalysisRequest) -> Result<Vec<Issue>> {
165        let mut issues = Vec::new();
166
167        for (line_num, line) in request.content.lines().enumerate() {
168            let line_number = line_num + 1;
169            let line_lower = line.to_lowercase();
170
171            // SECURITY PATTERNS
172
173            // Hardcoded credentials
174            if (line_lower.contains("password")
175                || line_lower.contains("api_key")
176                || line_lower.contains("secret"))
177                && line.contains("=")
178                && (line.contains("\"") || line.contains("'"))
179            {
180                issues.push(Issue {
181                    file: request.file_path.clone(),
182                    line: line_number,
183                    severity: "Critical".to_string(),
184                    category: "Security".to_string(),
185                    description: "Hardcoded credentials detected - use environment variables"
186                        .to_string(),
187                    commit_status: request.commit_status.clone(),
188                });
189            }
190
191            // Code injection
192            if line.contains("eval(") || line.contains("exec(") {
193                issues.push(Issue {
194                    file: request.file_path.clone(),
195                    line: line_number,
196                    severity: "Critical".to_string(),
197                    category: "Security".to_string(),
198                    description: "Code injection vulnerability - avoid eval/exec".to_string(),
199                    commit_status: request.commit_status.clone(),
200                });
201            }
202
203            // SQL injection patterns
204            if line.contains("query")
205                && line.contains("format!")
206                && (line.contains("SELECT") || line.contains("INSERT") || line.contains("UPDATE"))
207            {
208                issues.push(Issue {
209                    file: request.file_path.clone(),
210                    line: line_number,
211                    severity: "Critical".to_string(),
212                    category: "Security".to_string(),
213                    description: "Potential SQL injection - use parameterized queries".to_string(),
214                    commit_status: request.commit_status.clone(),
215                });
216            }
217
218            // Command injection patterns
219            if (line.contains("Command::new")
220                || line.contains("subprocess")
221                || line.contains("system("))
222                && (line.contains("format!")
223                    || line.contains("user_input")
224                    || line.contains("args"))
225            {
226                issues.push(Issue {
227                    file: request.file_path.clone(),
228                    line: line_number,
229                    severity: "Critical".to_string(),
230                    category: "Security".to_string(),
231                    description: "Command injection vulnerability - sanitize inputs".to_string(),
232                    commit_status: request.commit_status.clone(),
233                });
234            }
235
236            // Path traversal patterns
237            if line.contains("../")
238                && (line.contains("read") || line.contains("open") || line.contains("file"))
239            {
240                issues.push(Issue {
241                    file: request.file_path.clone(),
242                    line: line_number,
243                    severity: "High".to_string(),
244                    category: "Security".to_string(),
245                    description: "Path traversal vulnerability - validate file paths".to_string(),
246                    commit_status: request.commit_status.clone(),
247                });
248            }
249
250            // PERFORMANCE PATTERNS
251
252            // Nested loops (O(n²) complexity)
253            if line.contains("for") && line.trim().starts_with("for") {
254                // Check if there's another for loop nearby (simple heuristic)
255                let lines: Vec<&str> = request.content.lines().collect();
256                for (idx, _) in lines
257                    .iter()
258                    .enumerate()
259                    .take(std::cmp::min(line_num + 10, lines.len()))
260                    .skip(line_num + 1)
261                {
262                    if lines[idx].trim().starts_with("for") {
263                        issues.push(Issue {
264                            file: request.file_path.clone(),
265                            line: line_number,
266                            severity: "Medium".to_string(),
267                            category: "Performance".to_string(),
268                            description: "Nested loops detected - consider optimization"
269                                .to_string(),
270                            commit_status: request.commit_status.clone(),
271                        });
272                        break;
273                    }
274                }
275            }
276
277            // Language-specific analysis
278            match request.language.as_str() {
279                "rust" => {
280                    // Security
281                    if line.contains("unsafe") {
282                        issues.push(Issue {
283                            file: request.file_path.clone(),
284                            line: line_number,
285                            severity: "High".to_string(),
286                            category: "Security".to_string(),
287                            description: "Unsafe code block - requires justification and review"
288                                .to_string(),
289                            commit_status: request.commit_status.clone(),
290                        });
291                    }
292
293                    if line.contains("std::ptr::null") {
294                        issues.push(Issue {
295                            file: request.file_path.clone(),
296                            line: line_number,
297                            severity: "Critical".to_string(),
298                            category: "Security".to_string(),
299                            description: "Null pointer dereference - will cause segfault"
300                                .to_string(),
301                            commit_status: request.commit_status.clone(),
302                        });
303                    }
304
305                    // Error handling
306                    if line.contains("unwrap()") && !line.contains("expect(") {
307                        issues.push(Issue {
308                            file: request.file_path.clone(),
309                            line: line_number,
310                            severity: "Medium".to_string(),
311                            category: "Error Handling".to_string(),
312                            description:
313                                "Use expect() or proper error handling instead of unwrap()"
314                                    .to_string(),
315                            commit_status: request.commit_status.clone(),
316                        });
317                    }
318
319                    // Performance
320                    if line.contains(".clone()") && line.contains("&") {
321                        issues.push(Issue {
322                            file: request.file_path.clone(),
323                            line: line_number,
324                            severity: "Low".to_string(),
325                            category: "Performance".to_string(),
326                            description: "Unnecessary clone - consider borrowing instead"
327                                .to_string(),
328                            commit_status: request.commit_status.clone(),
329                        });
330                    }
331                }
332                "python" => {
333                    // Security
334                    if line.contains("pickle.loads") && !line.contains("trusted") {
335                        issues.push(Issue {
336                            file: request.file_path.clone(),
337                            line: line_number,
338                            severity: "Critical".to_string(),
339                            category: "Security".to_string(),
340                            description: "Unsafe deserialization - pickle.loads is dangerous"
341                                .to_string(),
342                            commit_status: request.commit_status.clone(),
343                        });
344                    }
345
346                    if line.contains("yaml.load") && !line.contains("safe_load") {
347                        issues.push(Issue {
348                            file: request.file_path.clone(),
349                            line: line_number,
350                            severity: "High".to_string(),
351                            category: "Security".to_string(),
352                            description: "Use yaml.safe_load instead of yaml.load".to_string(),
353                            commit_status: request.commit_status.clone(),
354                        });
355                    }
356
357                    // Performance
358                    if line.contains("+=") && (line.contains("\"") || line.contains("'")) {
359                        issues.push(Issue {
360                            file: request.file_path.clone(),
361                            line: line_number,
362                            severity: "Medium".to_string(),
363                            category: "Performance".to_string(),
364                            description:
365                                "String concatenation in loop - use join() for better performance"
366                                    .to_string(),
367                            commit_status: request.commit_status.clone(),
368                        });
369                    }
370                }
371                "javascript" | "typescript" => {
372                    // Security
373                    if line.contains("innerHTML") && line.contains("+") {
374                        issues.push(Issue {
375                            file: request.file_path.clone(),
376                            line: line_number,
377                            severity: "High".to_string(),
378                            category: "Security".to_string(),
379                            description: "XSS vulnerability - validate before setting innerHTML"
380                                .to_string(),
381                            commit_status: request.commit_status.clone(),
382                        });
383                    }
384
385                    // Performance
386                    if line.contains("document.getElementById") && line.contains("for") {
387                        issues.push(Issue {
388                            file: request.file_path.clone(),
389                            line: line_number,
390                            severity: "Medium".to_string(),
391                            category: "Performance".to_string(),
392                            description: "DOM query in loop - cache the element reference"
393                                .to_string(),
394                            commit_status: request.commit_status.clone(),
395                        });
396                    }
397                }
398                _ => {}
399            }
400
401            // CODE QUALITY PATTERNS
402
403            if line.contains("TODO") || line.contains("FIXME") || line.contains("HACK") {
404                issues.push(Issue {
405                    file: request.file_path.clone(),
406                    line: line_number,
407                    severity: "Low".to_string(),
408                    category: "Code Quality".to_string(),
409                    description: "Code comment indicates incomplete implementation".to_string(),
410                    commit_status: request.commit_status.clone(),
411                });
412            }
413
414            // Long line detection
415            if line.len() > 120 {
416                issues.push(Issue {
417                    file: request.file_path.clone(),
418                    line: line_number,
419                    severity: "Low".to_string(),
420                    category: "Code Quality".to_string(),
421                    description: format!(
422                        "Line too long ({} chars) - consider breaking into multiple lines",
423                        line.len()
424                    ),
425                    commit_status: request.commit_status.clone(),
426                });
427            }
428        }
429
430        Ok(issues)
431    }
432
433    fn ai_enhanced_analysis(&self, request: &AnalysisRequest) -> Result<Vec<Issue>> {
434        let mut issues = Vec::new();
435
436        // Start with rule-based analysis as foundation
437        issues.extend(self.rule_based_analysis(request)?);
438
439        // Enhanced AI analysis - contextual understanding and deeper patterns
440        let content = &request.content;
441        let lines: Vec<&str> = content.lines().collect();
442
443        // SEMANTIC ANALYSIS PATTERNS
444
445        // Detect architectural patterns and anti-patterns
446        if self.detect_architecture_issues(&lines, request) {
447            issues.push(Issue {
448                file: request.file_path.clone(),
449                line: 1,
450                severity: "Medium".to_string(),
451                category: "Architecture".to_string(),
452                description: "Potential architectural issues detected - consider refactoring"
453                    .to_string(),
454                commit_status: request.commit_status.clone(),
455            });
456        }
457
458        // Analyze code complexity and maintainability
459        let complexity_score = self.calculate_complexity_score(&lines);
460        if complexity_score > 50 {
461            issues.push(Issue {
462                file: request.file_path.clone(),
463                line: 1,
464                severity: "Medium".to_string(),
465                category: "Maintainability".to_string(),
466                description: format!(
467                    "High complexity score ({complexity_score}) - consider breaking into smaller functions"
468                ),
469                commit_status: request.commit_status.clone(),
470            });
471        }
472
473        // Detect potential race conditions in concurrent code
474        if self.detect_race_conditions(&lines, request) {
475            issues.push(Issue {
476                file: request.file_path.clone(),
477                line: 1,
478                severity: "High".to_string(),
479                category: "Concurrency".to_string(),
480                description: "Potential race condition detected - review shared state access"
481                    .to_string(),
482                commit_status: request.commit_status.clone(),
483            });
484        }
485
486        // Analyze error handling patterns
487        if self.detect_error_handling_issues(&lines, request) {
488            issues.push(Issue {
489                file: request.file_path.clone(),
490                line: 1,
491                severity: "Medium".to_string(),
492                category: "Error Handling".to_string(),
493                description: "Inconsistent error handling patterns - standardize approach"
494                    .to_string(),
495                commit_status: request.commit_status.clone(),
496            });
497        }
498
499        // Performance analysis with context awareness
500        if self.detect_performance_issues(&lines, request) {
501            issues.push(Issue {
502                file: request.file_path.clone(),
503                line: 1,
504                severity: "Medium".to_string(),
505                category: "Performance".to_string(),
506                description: "Performance optimization opportunities identified".to_string(),
507                commit_status: request.commit_status.clone(),
508            });
509        }
510
511        Ok(issues)
512    }
513
514    fn detect_architecture_issues(&self, lines: &[&str], request: &AnalysisRequest) -> bool {
515        let mut method_count = 0;
516        let mut field_count = 0;
517
518        for line in lines {
519            let trimmed = line.trim();
520            match request.language.as_str() {
521                "rust" => {
522                    if trimmed.starts_with("fn ") {
523                        method_count += 1;
524                    }
525                    if trimmed.starts_with("let ")
526                        || trimmed.starts_with("const ")
527                        || trimmed.contains(": ")
528                    {
529                        field_count += 1;
530                    }
531                }
532                "python" => {
533                    if trimmed.starts_with("def ") {
534                        method_count += 1;
535                    }
536                    if trimmed.starts_with("self.") && trimmed.contains("=") {
537                        field_count += 1;
538                    }
539                }
540                "javascript" | "typescript" => {
541                    if trimmed.contains("function ")
542                        || (trimmed.contains("=>") && trimmed.contains("{"))
543                    {
544                        method_count += 1;
545                    }
546                    if trimmed.contains("this.") && trimmed.contains("=") {
547                        field_count += 1;
548                    }
549                }
550                _ => {}
551            }
552        }
553
554        // God class detection: too many methods and fields
555        method_count > 20 || field_count > 15
556    }
557
558    fn calculate_complexity_score(&self, lines: &[&str]) -> u32 {
559        let mut score = 0u32;
560
561        for line in lines {
562            let trimmed = line.trim();
563
564            // Control flow increases complexity
565            if trimmed.starts_with("if ")
566                || trimmed.starts_with("else")
567                || trimmed.starts_with("for ")
568                || trimmed.starts_with("while ")
569                || trimmed.starts_with("match ")
570                || trimmed.starts_with("switch")
571            {
572                score += 2;
573            }
574
575            // Nested structures increase complexity more
576            let indent_level = line.len() - line.trim_start().len();
577            if indent_level > 8 {
578                score += 1;
579            }
580
581            // Exception handling
582            if trimmed.contains("catch") || trimmed.contains("except") || trimmed.contains("rescue")
583            {
584                score += 1;
585            }
586        }
587
588        score
589    }
590
591    fn detect_race_conditions(&self, lines: &[&str], request: &AnalysisRequest) -> bool {
592        let mut has_shared_state = false;
593        let mut has_concurrent_access = false;
594
595        for line in lines {
596            let trimmed = line.trim().to_lowercase();
597
598            match request.language.as_str() {
599                "rust" => {
600                    // Shared state indicators
601                    if trimmed.contains("arc<")
602                        || trimmed.contains("mutex")
603                        || trimmed.contains("rwlock")
604                        || trimmed.contains("static mut")
605                    {
606                        has_shared_state = true;
607                    }
608
609                    // Concurrent access indicators
610                    if trimmed.contains("tokio::spawn")
611                        || trimmed.contains("thread::spawn")
612                        || trimmed.contains("async")
613                    {
614                        has_concurrent_access = true;
615                    }
616                }
617                "python" => {
618                    if trimmed.contains("threading")
619                        || trimmed.contains("multiprocessing")
620                        || trimmed.contains("asyncio")
621                    {
622                        has_concurrent_access = true;
623                    }
624                    if trimmed.contains("global") || trimmed.contains("shared") {
625                        has_shared_state = true;
626                    }
627                }
628                "javascript" | "typescript" => {
629                    if trimmed.contains("worker")
630                        || trimmed.contains("promise")
631                        || trimmed.contains("async")
632                    {
633                        has_concurrent_access = true;
634                    }
635                    if trimmed.contains("window.") || trimmed.contains("global") {
636                        has_shared_state = true;
637                    }
638                }
639                _ => {}
640            }
641        }
642
643        has_shared_state && has_concurrent_access
644    }
645
646    fn detect_error_handling_issues(&self, lines: &[&str], request: &AnalysisRequest) -> bool {
647        let mut error_patterns = Vec::new();
648        let mut total_lines = 0;
649
650        for line in lines {
651            total_lines += 1;
652            let trimmed = line.trim().to_lowercase();
653
654            match request.language.as_str() {
655                "rust" => {
656                    if trimmed.contains("unwrap()") {
657                        error_patterns.push("unwrap");
658                    }
659                    if trimmed.contains("expect(") {
660                        error_patterns.push("expect");
661                    }
662                    if trimmed.contains("?") {
663                        error_patterns.push("question_mark");
664                    }
665                }
666                "python" => {
667                    if trimmed.contains("except:") || trimmed.contains("except exception") {
668                        error_patterns.push("bare_except");
669                    }
670                    if trimmed.contains("raise") {
671                        error_patterns.push("raise");
672                    }
673                }
674                "javascript" | "typescript" => {
675                    if trimmed.contains("throw") {
676                        error_patterns.push("throw");
677                    }
678                    if trimmed.contains("catch") && trimmed.contains("console.error") {
679                        error_patterns.push("console_error");
680                    }
681                }
682                _ => {}
683            }
684        }
685
686        // Check for inconsistent error handling patterns
687        let unique_patterns: std::collections::HashSet<_> = error_patterns.into_iter().collect();
688        unique_patterns.len() > 2 && total_lines > 50
689    }
690
691    fn detect_performance_issues(&self, lines: &[&str], request: &AnalysisRequest) -> bool {
692        let mut performance_concerns = 0;
693
694        for line in lines {
695            let trimmed = line.trim().to_lowercase();
696
697            match request.language.as_str() {
698                "rust" => {
699                    // Memory allocations in loops
700                    if trimmed.contains("vec!") && trimmed.contains("for ") {
701                        performance_concerns += 1;
702                    }
703                    // String concatenation in loops
704                    if trimmed.contains("push_str") && trimmed.contains("for ") {
705                        performance_concerns += 1;
706                    }
707                }
708                "python" => {
709                    // List comprehensions that could be generators
710                    if trimmed.contains("[") && trimmed.contains("for ") && trimmed.contains("in ")
711                    {
712                        performance_concerns += 1;
713                    }
714                }
715                "javascript" | "typescript" => {
716                    // DOM manipulation in loops
717                    if trimmed.contains("getelement") && trimmed.contains("for ") {
718                        performance_concerns += 1;
719                    }
720                }
721                _ => {}
722            }
723        }
724
725        performance_concerns > 2
726    }
727
728    fn detect_language(&self, file_path: &str) -> String {
729        let path = Path::new(file_path);
730        match path.extension().and_then(|ext| ext.to_str()) {
731            Some("rs") => "rust".to_string(),
732            Some("js") => "javascript".to_string(),
733            Some("ts") => "typescript".to_string(),
734            Some("py") => "python".to_string(),
735            Some("java") => "java".to_string(),
736            Some("cpp") | Some("cc") | Some("cxx") => "cpp".to_string(),
737            Some("c") => "c".to_string(),
738            Some("go") => "go".to_string(),
739            Some("php") => "php".to_string(),
740            Some("rb") => "ruby".to_string(),
741            Some("cs") => "csharp".to_string(),
742            _ => "unknown".to_string(),
743        }
744    }
745}
746
747#[cfg(test)]
748mod tests {
749    use super::*;
750    use crate::core::review::CommitStatus;
751
752    fn make_request(file: &str, content: &str, language: &str) -> AnalysisRequest {
753        AnalysisRequest {
754            file_path: file.to_string(),
755            content: content.to_string(),
756            language: language.to_string(),
757            commit_status: CommitStatus::Modified,
758        }
759    }
760
761    #[test]
762    fn test_detect_language_variants() {
763        let analyzer = AIAnalyzer {
764            backend: GpuBackend::Cpu,
765            enable_ai: true,
766        };
767        assert_eq!(analyzer.detect_language("src/main.rs"), "rust");
768        assert_eq!(analyzer.detect_language("a/b/c.py"), "python");
769        assert_eq!(analyzer.detect_language("index.ts"), "typescript");
770        assert_eq!(analyzer.detect_language("script.js"), "javascript");
771        assert_eq!(analyzer.detect_language("unknown.foo"), "unknown");
772    }
773
774    #[test]
775    fn test_rule_based_analysis_rust_patterns() {
776        let analyzer = AIAnalyzer {
777            backend: GpuBackend::Cpu,
778            enable_ai: true,
779        };
780        let content = r#"
781            // SECURITY
782            let password = "secret";
783            let _ = eval("2+2");
784            let query = format!("SELECT * FROM users");
785            std::process::Command::new("sh").arg(format!("{}", user_input));
786            let _ = std::fs::read("../etc/passwd");
787            // PERFORMANCE
788            for i in 0..10 {
789                for j in 0..10 {}
790            }
791            // RUST SPECIFIC
792            unsafe { /* do unsafe things */ }
793            let p = std::ptr::null();
794            let _ = something.unwrap();
795            let _y = &x.clone();
796            // QUALITY
797            // TODO: fix
798            // Long line next
799            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
800        "#;
801        let req = make_request("file.rs", content, "rust");
802        let issues = analyzer.rule_based_analysis(&req).unwrap();
803        assert!(!issues.is_empty());
804        // Ensure we hit multiple categories
805        assert!(issues.iter().any(|i| i.category == "Security"));
806        assert!(issues.iter().any(|i| i.category == "Performance"));
807        assert!(issues.iter().any(|i| i.category == "Code Quality"));
808    }
809
810    #[test]
811    fn test_rule_based_analysis_python_patterns() {
812        let analyzer = AIAnalyzer {
813            backend: GpuBackend::Cpu,
814            enable_ai: true,
815        };
816        let content = r#"
817            import pickle
818            data = pickle.loads(b"...")
819            import yaml
820            result = yaml.load("x: 1")
821            s = "";
822            for i in range(10): s += "x"
823        "#;
824        let req = make_request("script.py", content, "python");
825        let issues = analyzer.rule_based_analysis(&req).unwrap();
826        assert!(issues.iter().any(|i| i.category == "Security"));
827        assert!(issues.iter().any(|i| i.category == "Performance"));
828    }
829
830    #[test]
831    fn test_rule_based_analysis_js_patterns() {
832        let analyzer = AIAnalyzer {
833            backend: GpuBackend::Cpu,
834            enable_ai: true,
835        };
836        let content = r#"
837            let x = "user";
838            element.innerHTML = "<div>" + x;
839            for (let i = 0; i < 10; i++) { document.getElementById("id"); }
840        "#;
841        let req = make_request("script.js", content, "javascript");
842        let issues = analyzer.rule_based_analysis(&req).unwrap();
843        assert!(issues.iter().any(|i| i.category == "Security"));
844        assert!(issues.iter().any(|i| i.category == "Performance"));
845    }
846
847    #[test]
848    fn test_analyze_file_emits_progress_and_issues() {
849        let rt = tokio::runtime::Runtime::new().unwrap();
850        rt.block_on(async {
851            let analyzer = AIAnalyzer::new(false, true).await.unwrap();
852            let (tx, mut rx) = mpsc::unbounded_channel::<ProgressUpdate>();
853            let req = make_request("file.rs", "let password = \"x\";", "rust");
854            let issues = analyzer.analyze_file(req, Some(tx)).await.unwrap();
855            assert!(!issues.is_empty());
856            // Try receive up to a couple of progress messages (non-blocking)
857            let mut got_any = false;
858            for _ in 0..4 {
859                if rx.try_recv().is_ok() {
860                    got_any = true;
861                    break;
862                }
863            }
864            assert!(got_any, "expected at least one progress message");
865        });
866    }
867}