use crate::invariant::rules::util::{list_files_with_ext, rel};
use crate::invariant::{Category, Context, Invariant, Outcome};
use std::fs;
const SKIP_FILES: &[&str] = &[
"crates/koala-core/src/invariant/rules/no_unsafe.rs",
"crates/koala-health/src/collect.rs",
"crates/koala-health/tests/snapshot.rs",
];
pub struct NoUnsafe;
impl Invariant for NoUnsafe {
fn id(&self) -> &'static str {
"arch.no-unsafe"
}
fn category(&self) -> Category {
Category::Arch
}
fn intent(&self) -> &'static str {
"No `unsafe` blocks, fns, impls, or traits in workspace Rust source."
}
fn adr(&self) -> Option<&'static str> {
Some("ADR-0013")
}
fn evaluate(&self, ctx: &Context) -> Outcome {
let mut hits = Vec::new();
for path in list_files_with_ext(ctx.root(), "rs") {
let r = rel(&path, ctx.root());
if SKIP_FILES.iter().any(|s| r == *s) {
continue;
}
let Ok(content) = fs::read_to_string(&path) else {
continue;
};
for (i, line) in content.lines().enumerate() {
let trimmed = line.trim_start();
if trimmed.starts_with("//") || trimmed.starts_with("/*") {
continue;
}
let is_hit = trimmed.starts_with("unsafe ")
|| trimmed.starts_with("unsafe{")
|| trimmed.contains(" unsafe ")
|| trimmed.contains(" unsafe{");
if is_hit {
hits.push(format!("{}:{}", r, i + 1));
}
}
}
if hits.is_empty() {
Outcome::pass()
} else {
Outcome::fail_repro(
format!(
"found {} unsafe occurrence(s):\n {}",
hits.len(),
hits.join("\n ")
),
"rg -nP '(^|\\s)unsafe\\s*[\\{a-z]' --type rust",
)
}
}
}