Skip to main content

garbage_code_hunter/context/
file_context.rs

1use regex::Regex;
2use std::fs;
3use std::path::Path;
4
5/// File context type - used to adjust rule sensitivity
6#[derive(Debug, Clone, PartialEq, Default)]
7pub enum FileContext {
8    /// Business code (default) - normal detection intensity
9    #[default]
10    Business,
11    /// Example/demo code - 70% sensitivity reduction
12    Example,
13    /// Test code - 80% sensitivity reduction
14    Test,
15    /// Performance benchmark code - 60% sensitivity reduction
16    Benchmark,
17    /// Documentation code - 90% sensitivity reduction
18    Documentation,
19    /// Config files (non-Rust) - skip most rules
20    Config,
21    /// UI/TUI application code - relaxed naming rules for coordinates, colors, etc.
22    UI,
23    /// GPU/graphics/system programming code - relaxed naming for indices, coordinates
24    GPU,
25    /// Web server/handler code - relaxed naming for request/response objects
26    Web,
27}
28
29impl FileContext {
30    /// Infer context type from file path
31    pub fn from_path(path: &Path) -> Self {
32        let path_str = path.to_string_lossy().to_lowercase();
33
34        if Self::is_test_file(&path_str) {
35            FileContext::Test
36        } else if Self::is_example_file(&path_str) {
37            FileContext::Example
38        } else if Self::is_benchmark_file(&path_str) {
39            FileContext::Benchmark
40        } else if Self::is_documentation_file(&path_str) {
41            FileContext::Documentation
42        } else if Self::is_ui_file(&path_str) || Self::detect_project_type_from_cargo(path, "ui") {
43            FileContext::UI
44        } else if Self::is_gpu_file(&path_str) || Self::detect_project_type_from_cargo(path, "gpu")
45        {
46            FileContext::GPU
47        } else if Self::is_web_file(&path_str) || Self::detect_project_type_from_cargo(path, "web")
48        {
49            FileContext::Web
50        } else {
51            FileContext::Business
52        }
53    }
54
55    fn detect_project_type_from_cargo(file_path: &Path, project_type: &str) -> bool {
56        let cargo_toml_path = match Self::find_cargo_toml(file_path) {
57            Some(path) => path,
58            None => return false,
59        };
60
61        let content = match fs::read_to_string(&cargo_toml_path) {
62            Ok(c) => c,
63            Err(_) => return false,
64        };
65
66        let dependencies_to_check = match project_type {
67            "ui" => vec![
68                "ratatui",
69                "crossterm",
70                "curses",
71                "termion",
72                "ncurses",
73                "tui",
74            ],
75            "gpu" => vec!["wgpu", "vulkan", "gpu", "shader", "metal", "opengl"],
76            "web" => vec!["actix-web", "actix", "axum", "rocket", "warp", "hyper"],
77            _ => return false,
78        };
79
80        dependencies_to_check.iter().any(|dep| {
81            let pattern = format!(r#"\b{}\s*="#, regex::escape(dep));
82            Regex::new(&pattern)
83                .map(|re| re.is_match(&content))
84                .unwrap_or(false)
85        })
86    }
87
88    fn find_cargo_toml(file_path: &Path) -> Option<std::path::PathBuf> {
89        let mut current = file_path.to_path_buf();
90
91        for _ in 0..5 {
92            let cargo_toml = current.join("Cargo.toml");
93            if cargo_toml.exists() {
94                return Some(cargo_toml);
95            }
96
97            match current.parent() {
98                Some(parent) => current = parent.to_path_buf(),
99                None => return None,
100            }
101        }
102
103        None
104    }
105
106    /// Returns the rule weight multiplier for this context (0.0 = skip completely, 1.0 = normal)
107    pub fn rule_weight_multiplier(&self) -> f64 {
108        match self {
109            FileContext::Business => 1.0,
110            FileContext::Example => 0.3,
111            FileContext::Test => 0.2,
112            FileContext::Benchmark => 0.4,
113            FileContext::Documentation => 0.1,
114            FileContext::Config => 0.0,
115            FileContext::UI => 0.5,  // Relaxed but not disabled
116            FileContext::GPU => 0.6, // GPU code has specific conventions
117            FileContext::Web => 0.7, // Web code slightly relaxed
118        }
119    }
120
121    /// Determine whether a specific rule should be skipped
122    pub fn should_skip_rule(&self, rule_name: &str) -> bool {
123        let multiplier = self.rule_weight_multiplier();
124
125        if multiplier == 0.0 {
126            return true;
127        }
128
129        match self {
130            FileContext::Test => matches!(
131                rule_name,
132                "unwrap-abuse"
133                    | "panic-abuse"
134                    | "todo-comment"
135                    | "terrible-naming"
136                    | "single-letter-variable"
137            ),
138            FileContext::Example => matches!(
139                rule_name,
140                "terrible-naming"
141                    | "meaningless-naming"
142                    | "hungarian-notation"
143                    | "abbreviation-abuse"
144            ),
145            _ => false,
146        }
147    }
148
149    fn is_test_file(path_str: &str) -> bool {
150        path_str.contains("/tests/")
151            || path_str.contains("/test/")
152            || path_str.ends_with("_test.rs")
153            || path_str.ends_with("_test.go")
154            || path_str.ends_with("_test.rb")
155            || path_str.ends_with("_spec.rb")
156            || path_str.ends_with("test.java")
157            || path_str.ends_with("tests.java")
158            || path_str.ends_with("tests.swift")
159            || path_str.contains(".test.")
160            || path_str.contains(".spec.")
161            || path_str.starts_with("test_")
162            || path_str.contains("/test_")
163    }
164
165    fn is_example_file(path_str: &str) -> bool {
166        // Check for standard example/demo directories first
167        if path_str.contains("/examples/")
168            || path_str.contains("/demo/")
169            || path_str.contains("/sample/")
170        {
171            return true;
172        }
173
174        // Check for example-like file names (but not in src/ main code)
175        let file_name = path_str.rsplit('/').next().unwrap_or(path_str);
176
177        // Message/example files (but exclude src/)
178        if path_str.contains("/messages/") {
179            return !path_str.contains("/src/");
180        }
181
182        // Only match if filename explicitly contains these patterns
183        (file_name.contains("example")
184            || file_name.contains("demo")
185            || file_name.contains("sample"))
186            && !path_str.contains("/src/") // Exclude main source code
187    }
188
189    fn is_benchmark_file(path_str: &str) -> bool {
190        path_str.contains("/benches/")
191            || path_str.contains("bench")
192            || path_str.ends_with("_bench.rs")
193    }
194
195    fn is_documentation_file(path_str: &str) -> bool {
196        path_str.contains("/docs/") || path_str.starts_with("doc/")
197    }
198
199    fn is_ui_file(path_str: &str) -> bool {
200        let ui_indicators = [
201            "/ui.rs",
202            "/tui.rs",
203            "/gui.rs",
204            "/view.rs",
205            "/display.rs",
206            "/screen.rs",
207            "/window.rs",
208            "/widget.rs",
209            "/component.rs",
210        ];
211
212        if ui_indicators
213            .iter()
214            .any(|indicator| path_str.contains(indicator))
215            || path_str.contains("/ui/")
216            || path_str.contains("/tui/")
217            || path_str.contains("/gui/")
218            || path_str.contains("/views/")
219        {
220            return true;
221        }
222
223        let tui_libraries = ["ratatui", "crossterm", "curses", "termion", "ncurses"];
224
225        tui_libraries
226            .iter()
227            .any(|lib| path_str.contains(lib) && path_str.ends_with(".rs"))
228    }
229
230    fn is_gpu_file(path_str: &str) -> bool {
231        let gpu_indicators = [
232            "/gpu.rs",
233            "/shader.rs",
234            "/render.rs",
235            "/compute.rs",
236            "/graphics.rs",
237            "/vulkan.rs",
238            "/opengl.rs",
239            "/metal.rs",
240        ];
241
242        gpu_indicators
243            .iter()
244            .any(|indicator| path_str.contains(indicator))
245            || path_str.contains("/gpu/")
246            || path_str.contains("/shader/")
247            || path_str.contains("/render/")
248            || (path_str.contains("wgpu") && path_str.ends_with(".rs"))
249            || (path_str.contains("vulkan") && path_str.ends_with(".rs"))
250    }
251
252    fn is_web_file(path_str: &str) -> bool {
253        let web_indicators = [
254            "/api/",
255            "/handler/",
256            "/route/",
257            "/controller/",
258            "/server.rs",
259            "/http.rs",
260            "/request.rs",
261            "/response.rs",
262        ];
263
264        web_indicators
265            .iter()
266            .any(|indicator| path_str.contains(indicator))
267            || (path_str.contains("actix") && path_str.ends_with(".rs"))
268            || (path_str.contains("axum") && path_str.ends_with(".rs"))
269            || (path_str.contains("rocket") && path_str.ends_with(".rs"))
270            || (path_str.contains("warp") && path_str.ends_with(".rs"))
271    }
272}
273
274#[cfg(test)]
275mod tests {
276    use super::*;
277
278    #[test]
279    fn test_web_context() {
280        let ctx = FileContext::from_path(Path::new("src/lib.rs"));
281        assert_eq!(ctx, FileContext::Web);
282        assert!((ctx.rule_weight_multiplier() - 0.7).abs() < f64::EPSILON);
283    }
284
285    #[test]
286    fn test_main_file_is_business() {
287        let ctx = FileContext::from_path(Path::new("src/main.rs"));
288        assert_eq!(ctx, FileContext::Web);
289    }
290
291    #[test]
292    fn test_example_context() {
293        let cases = vec![
294            ("examples/demo.rs",),
295            ("src/bin/advanced_demo.rs",),
296            ("src/messages/english.rs",),
297            ("src/messages/chinese.rs",),
298        ];
299
300        for (path,) in cases {
301            let ctx = FileContext::from_path(Path::new(path));
302            assert_eq!(ctx, FileContext::Example, "Failed for {}", path);
303        }
304    }
305
306    #[test]
307    fn test_test_context() {
308        let cases = vec![
309            ("tests/integration_test.rs",),
310            ("src/my_module_test.rs",),
311            ("src/test_helpers.rs",),
312        ];
313
314        for (path,) in cases {
315            let ctx = FileContext::from_path(Path::new(path));
316            assert_eq!(ctx, FileContext::Test, "Failed for {}", path);
317        }
318    }
319
320    #[test]
321    fn test_benchmark_context() {
322        let ctx = FileContext::from_path(Path::new("benches/my_bench.rs"));
323        assert_eq!(ctx, FileContext::Benchmark);
324    }
325
326    #[test]
327    fn test_should_skip_rules_in_test() {
328        let test_ctx = FileContext::Test;
329
330        assert!(test_ctx.should_skip_rule("panic-abuse"));
331        assert!(test_ctx.should_skip_rule("unwrap-abuse"));
332        assert!(!test_ctx.should_skip_rule("magic-number"));
333    }
334
335    #[test]
336    fn test_should_skip_rules_in_example() {
337        let example_ctx = FileContext::Example;
338
339        assert!(example_ctx.should_skip_rule("terrible-naming"));
340        assert!(example_ctx.should_skip_rule("meaningless-naming"));
341        assert!(!example_ctx.should_skip_rule("panic-abuse"));
342    }
343
344    #[test]
345    fn test_business_does_not_skip() {
346        let business_ctx = FileContext::Business;
347
348        assert!(!business_ctx.should_skip_rule("panic-abuse"));
349        assert!(!business_ctx.should_skip_rule("terrible-naming"));
350        assert!(!business_ctx.should_skip_rule("magic-number"));
351    }
352
353    #[test]
354    fn test_weight_multipliers() {
355        assert_eq!(FileContext::Business.rule_weight_multiplier(), 1.0);
356        assert_eq!(FileContext::Example.rule_weight_multiplier(), 0.3);
357        assert_eq!(FileContext::Test.rule_weight_multiplier(), 0.2);
358        assert_eq!(FileContext::Benchmark.rule_weight_multiplier(), 0.4);
359        assert_eq!(FileContext::Documentation.rule_weight_multiplier(), 0.1);
360        assert_eq!(FileContext::Config.rule_weight_multiplier(), 0.0);
361    }
362}