use std::collections::BTreeMap;
use std::fs;
use std::path::{Path, PathBuf};
const ALLOWED: &[(&str, usize, &str)] = &[];
fn rust_files(root: &Path, out: &mut Vec<PathBuf>) {
for entry in fs::read_dir(root).expect("read_dir") {
let path = entry.expect("entry").path();
if path.is_dir() {
rust_files(&path, out);
} else if path.extension().is_some_and(|e| e == "rs") && !is_test_file(&path) {
out.push(path);
}
}
}
fn is_test_file(path: &Path) -> bool {
let name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
name == "tests.rs"
|| name.starts_with("tests")
|| name.ends_with("tests.inc.rs")
|| name.ends_with("_tests.rs")
}
fn is_test_cfg(trimmed: &str) -> bool {
trimmed.starts_with("#[cfg(") && trimmed.contains("test") && !trimmed.contains("not(test")
}
fn indent_of(line: &str) -> usize {
line.len() - line.trim_start().len()
}
fn production_lines(source: &str) -> Vec<(usize, &str)> {
let lines: Vec<&str> = source.lines().collect();
let mut out = Vec::new();
let mut i = 0;
while i < lines.len() {
if !is_test_cfg(lines[i].trim()) {
out.push((i + 1, lines[i]));
i += 1;
continue;
}
let Some(offset) = lines[i + 1..].iter().position(|l| !l.trim().is_empty()) else {
break;
};
let first = i + 1 + offset;
let head = lines[first].trim();
if head.starts_with("mod ") || head.starts_with("pub mod ") {
break;
}
let indent = indent_of(lines[i]);
let mut end = first;
if !lines[first].trim_end().ends_with(';') {
while end < lines.len()
&& !(indent_of(lines[end]) == indent && lines[end].trim_start().starts_with('}'))
{
end += 1;
}
}
i = end + 1;
}
out
}
fn silent_discards(source: &str) -> usize {
production_lines(source)
.iter()
.filter(|(_, l)| {
let t = l.trim();
t.starts_with("let _ =") || t.starts_with("let _:")
})
.count()
}
fn collect_counts(count: impl Fn(&str) -> usize) -> BTreeMap<String, usize> {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let mut files = Vec::new();
rust_files(&root.join("src"), &mut files);
let mut actual: BTreeMap<String, usize> = BTreeMap::new();
for file in files {
let source = fs::read_to_string(&file).expect("read source");
let n = count(&source);
if n > 0 {
let rel = file
.strip_prefix(root)
.expect("under crate root")
.to_string_lossy()
.replace('\\', "/");
actual.insert(rel, n);
}
}
actual
}
#[test]
fn let_underscore_stays_within_the_documented_allowance() {
let actual = collect_counts(silent_discards);
let mut problems = Vec::new();
for (file, n) in &actual {
let allowed = ALLOWED
.iter()
.find(|(f, _, _)| f == file)
.map_or(0, |(_, c, _)| *c);
if *n > allowed {
problems.push(format!(
"{file}: {n} `let _ =` found, {allowed} allowed (propagate with `?` or use best_effort::best_effort)"
));
}
}
for (file, allowed, why) in ALLOWED {
let n = actual.get(*file).copied().unwrap_or(0);
if n < *allowed && !why.starts_with("PENDING") {
problems.push(format!(
"{file}: allowance is {allowed} but only {n} remain (tighten or remove the entry)"
));
}
}
assert!(problems.is_empty(), "\n{}", problems.join("\n"));
}
#[test]
fn every_allowance_states_a_reason() {
for (file, count, why) in ALLOWED {
assert!(*count > 0, "{file}: a zero allowance is just no entry");
assert!(why.len() > 10, "{file}: the reason is too short to be one");
}
}
#[test]
fn counter_ignores_comments_and_test_code() {
let src = "// let _ = a();\nfn f() { let _ = b(); }\nlet _ = c();\n#[cfg(test)]\nmod tests {\n let _ = d();\n}\n";
assert_eq!(silent_discards(src), 1);
let guarded = "#[cfg(test)]\nfn only_in_tests() {\n let _ = f();\n}\nlet _ = e();\n";
assert_eq!(silent_discards(guarded), 1);
let one_line = "#[cfg(test)]\nuse std::fmt;\nlet _ = g();\n";
assert_eq!(silent_discards(one_line), 1);
}
const PANIC_NEEDLES: &[&str] = &[
".unwrap()",
".expect(",
"unreachable!(",
"panic!(",
"todo!(",
"unimplemented!(",
];
fn panics_in_production(source: &str) -> usize {
let mut found = 0;
for (_, line) in production_lines(source) {
if line.trim_start().starts_with("//") {
continue;
}
for needle in PANIC_NEEDLES {
let mut from = 0;
while let Some(at) = line[from..].find(needle) {
let start = from + at;
let glued = !needle.starts_with('.')
&& line[..start]
.chars()
.next_back()
.is_some_and(|c| c.is_alphanumeric() || c == '_');
if !glued {
found += 1;
}
from = start + needle.len();
}
}
}
found
}
const ALLOWED_PANICS: &[(&str, usize, &str)] = &[];
#[test]
fn production_code_does_not_panic() {
let actual = collect_counts(panics_in_production);
let mut problems = Vec::new();
for (file, n) in &actual {
let allowed = ALLOWED_PANICS
.iter()
.find(|(f, _, _)| f == file)
.map_or(0, |(_, c, _)| *c);
if *n > allowed {
problems.push(format!(
"{file}: {n} panicking call(s), {allowed} allowed (return a typed error such as AtomwriteError::InternalError)"
));
}
}
assert!(problems.is_empty(), "\n{}", problems.join("\n"));
}
#[test]
fn panic_counter_sees_calls_and_skips_tests_comments_and_glued_names() {
let src = "fn f() { x.unwrap(); }\n// y.unwrap();\nhuman_panic::setup_panic!();\nfn g() { panic!(\"no\"); }\n#[cfg(test)]\nmod tests {\n fn t() { z.expect(\"e\"); }\n}\n";
assert_eq!(panics_in_production(src), 2);
let test_only =
"#[cfg(test)]\nfn helper() {\n a.expect(\"b\")\n}\nfn real() { c.unwrap() }\n";
assert_eq!(panics_in_production(test_only), 1);
}