garbage_code_hunter/
analyzer.rs1use regex::Regex;
2use std::fs;
3use std::path::{Path, PathBuf};
4use syn::parse_file;
5use walkdir::WalkDir;
6
7use crate::rules::RuleEngine;
8
9#[derive(Debug, Clone)]
10pub struct CodeIssue {
11 pub file_path: PathBuf,
12 pub line: usize,
13 pub column: usize,
14 pub rule_name: String,
15 pub message: String,
16 pub severity: Severity,
17 #[allow(dead_code)]
18 pub roast_level: RoastLevel,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq, Hash)]
22pub enum Severity {
23 Mild, Spicy, Nuclear, }
27
28#[derive(Debug, Clone, PartialEq)]
29pub enum RoastLevel {
30 Gentle, Sarcastic, Savage, }
34
35pub struct CodeAnalyzer {
36 rule_engine: RuleEngine,
37 exclude_patterns: Vec<Regex>,
38 lang: String,
39}
40
41impl CodeAnalyzer {
42 pub fn new(exclude_patterns: &[String], lang: &str) -> Self {
43 let patterns = exclude_patterns
44 .iter()
45 .filter_map(|pattern| {
46 let regex_pattern = pattern
48 .replace(".", r"\.")
49 .replace("*", ".*")
50 .replace("?", ".");
51 Regex::new(®ex_pattern).ok()
52 })
53 .collect();
54
55 Self {
56 rule_engine: RuleEngine::new(),
57 exclude_patterns: patterns,
58 lang: lang.to_string(),
59 }
60 }
61
62 fn should_exclude(&self, path: &Path) -> bool {
63 let path_str = path.to_string_lossy();
64 self.exclude_patterns
65 .iter()
66 .any(|pattern| pattern.is_match(&path_str))
67 }
68
69 pub fn analyze_path(&self, path: &Path) -> Vec<CodeIssue> {
70 let mut issues = Vec::new();
71
72 if path.is_file() {
73 if !self.should_exclude(path) {
74 if let Some(ext) = path.extension() {
75 if ext == "rs" {
76 issues.extend(self.analyze_file(path));
77 }
78 }
79 }
80 } else if path.is_dir() {
81 for entry in WalkDir::new(path)
82 .into_iter()
83 .filter_map(|e| e.ok())
84 .filter(|e| !self.should_exclude(e.path()))
85 .filter(|e| e.path().extension().is_some_and(|ext| ext == "rs"))
86 {
87 issues.extend(self.analyze_file(entry.path()));
88 }
89 }
90
91 issues
92 }
93
94 pub fn analyze_file(&self, file_path: &Path) -> Vec<CodeIssue> {
95 let content = match fs::read_to_string(file_path) {
96 Ok(content) => content,
97 Err(_) => return vec![],
98 };
99
100 let syntax_tree = match parse_file(&content) {
101 Ok(tree) => tree,
102 Err(_) => return vec![],
103 };
104
105 self.rule_engine
106 .check_file(file_path, &syntax_tree, &content, &self.lang)
107 }
108}