impl RustDefectDetector {
pub fn new() -> Self {
Self {
unwrap_regex: Regex::new(r"\.unwrap\(\)").expect("internal error"),
}
}
fn should_exclude_file(&self, file_path: &Path) -> bool {
let path_str = file_path.to_string_lossy();
let file_name = file_path.file_name().and_then(|n| n.to_str()).unwrap_or("");
if path_str.contains("/tests/")
|| path_str.starts_with("tests/")
|| path_str.contains("/benches/")
|| path_str.starts_with("benches/")
{
return true;
}
if path_str.contains("/examples/")
|| path_str.starts_with("examples/")
|| path_str.starts_with("./examples/")
{
return true;
}
if path_str.contains("/fuzz/")
|| path_str.starts_with("fuzz/")
|| path_str.starts_with("./fuzz/")
{
return true;
}
if file_name.ends_with("_tests.rs")
|| file_name.ends_with("_test.rs")
|| file_name.starts_with("test_")
{
return true;
}
false
}
fn has_test_markers(&self, content: &str) -> bool {
let has_cfg_test = content.contains("#[cfg(test)]")
|| content.contains("#[cfg(all(test,")
|| content.contains("#[cfg(any(test,");
let has_test_attr = content.contains("#[test]")
|| content.contains("#[tokio::test]")
|| content.contains("#[async_test]");
has_cfg_test || has_test_attr
}
pub fn detect(&self, content: &str, file_path: &Path) -> Vec<DefectPattern> {
let mut defects = Vec::new();
if self.should_exclude_file(file_path) {
return defects;
}
if self.has_test_markers(content) {
return defects;
}
let unwrap_instances = self.detect_unwraps(content, file_path);
if !unwrap_instances.is_empty() {
defects.push(DefectPattern {
id: "RUST-UNWRAP-001".to_string(),
name: ".unwrap() calls".to_string(),
severity: Severity::Critical,
fix_recommendation:
"Use .expect() with descriptive messages or proper error handling with ?"
.to_string(),
bad_example: "let x = result.unwrap();".to_string(),
good_example: "let x = result.expect(\"Bot feature file must be valid\");"
.to_string(),
evidence_description: "Cloudflare outage 2025-11-18 (3+ hour network outage)"
.to_string(),
evidence_url: Some("https://blog.cloudflare.com/2025-01-18-outage".to_string()),
instances: unwrap_instances,
});
}
defects
}
fn detect_unwraps(&self, content: &str, file_path: &Path) -> Vec<DefectInstance> {
let mut instances = Vec::new();
for (line_num, line) in content.lines().enumerate() {
let trimmed = line.trim();
if trimmed.starts_with("///") || trimmed.starts_with("//!") {
continue;
}
for mat in self.unwrap_regex.find_iter(line) {
instances.push(DefectInstance {
file: file_path.to_string_lossy().to_string(),
line: line_num + 1,
column: mat.start() + 1,
code_snippet: line.trim().to_string(),
});
}
}
instances
}
pub fn count_unwraps(&self, content: &str) -> usize {
self.unwrap_regex.find_iter(content).count()
}
}
impl Default for RustDefectDetector {
fn default() -> Self {
Self::new()
}
}