use std::{
fs,
path::{Path, PathBuf},
};
const RENDER_VIOLATION_BASELINE: usize = 846;
#[test]
fn cli_commands_render_via_render_or_write_functions() {
let commands_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("src")
.join("cli")
.join("commands");
assert!(
commands_dir.is_dir(),
"commands directory missing at {}",
commands_dir.display()
);
let mut total = 0usize;
let mut by_file: Vec<(PathBuf, usize)> = Vec::new();
walk_rust_files(&commands_dir, &mut |path| {
let source = match fs::read_to_string(path) {
Ok(s) => s,
Err(_) => return,
};
let count = count_violations(&source);
if count > 0 {
by_file.push((path.to_path_buf(), count));
total += count;
}
});
if total > RENDER_VIOLATION_BASELINE {
by_file.sort_by_key(|entry| std::cmp::Reverse(entry.1));
let lines: Vec<String> = by_file
.iter()
.take(10)
.map(|(p, n)| {
format!(
" {n:4} {}",
p.strip_prefix(&commands_dir).unwrap_or(p).display()
)
})
.collect();
panic!(
"render-discipline regression: {total} println!/print! calls outside \
render_*/write_* functions (baseline {RENDER_VIOLATION_BASELINE}).\n\
Top offenders:\n{}\n\n\
A new violation was introduced. Either route the output through a \
`render_<thing>` or `write_<thing>` function and a `*Output` struct, \
or — if you removed violations elsewhere — lower \
RENDER_VIOLATION_BASELINE in this test by the matching count.",
lines.join("\n"),
);
}
}
fn walk_rust_files(dir: &Path, visit: &mut dyn FnMut(&Path)) {
let Ok(entries) = fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
let name = path.file_name().and_then(|s| s.to_str()).unwrap_or("");
if name.starts_with('.') {
continue;
}
if path.is_dir() {
walk_rust_files(&path, visit);
} else if path.extension().and_then(|e| e.to_str()) == Some("rs") {
visit(&path);
}
}
}
fn count_violations(source: &str) -> usize {
let bytes = source.as_bytes();
let mut violations = 0usize;
let mut i = 0usize;
let len = bytes.len();
let mut exempt_stack: Vec<bool> = vec![false];
let mut next_scope_exempt = false;
while i < len {
let c = bytes[i];
if c == b'/' && bytes.get(i + 1) == Some(&b'/') {
while i < len && bytes[i] != b'\n' {
i += 1;
}
continue;
}
if c == b'/' && bytes.get(i + 1) == Some(&b'*') {
i += 2;
while i + 1 < len && !(bytes[i] == b'*' && bytes[i + 1] == b'/') {
i += 1;
}
i = (i + 2).min(len);
continue;
}
if c == b'"' {
i += 1;
while i < len && bytes[i] != b'"' {
if bytes[i] == b'\\' {
i += 2;
continue;
}
i += 1;
}
i += 1;
continue;
}
if c == b'\'' {
i += 1;
while i < len && bytes[i] != b'\'' && bytes[i] != b'\n' {
if bytes[i] == b'\\' {
i += 2;
continue;
}
i += 1;
}
if i < len && bytes[i] == b'\'' {
i += 1;
}
continue;
}
if c == b'#'
&& bytes.get(i + 1) == Some(&b'[')
&& rest_starts_with(bytes, i, "#[cfg(test)]")
{
next_scope_exempt = true;
i += "#[cfg(test)]".len();
continue;
}
if rest_starts_with(bytes, i, "mod tests") {
next_scope_exempt = true;
i += "mod tests".len();
continue;
}
if rest_starts_with(bytes, i, "fn render_") || rest_starts_with(bytes, i, "fn write_") {
next_scope_exempt = true;
i += 2; continue;
}
if c == b'{' {
exempt_stack.push(next_scope_exempt || *exempt_stack.last().unwrap_or(&false));
next_scope_exempt = false;
i += 1;
continue;
}
if c == b'}' {
exempt_stack.pop();
if exempt_stack.is_empty() {
exempt_stack.push(false);
}
i += 1;
continue;
}
if rest_starts_with(bytes, i, "println!") && !*exempt_stack.last().unwrap_or(&false) {
violations += 1;
i += "println!".len();
continue;
}
if rest_starts_with(bytes, i, "print!")
&& !*exempt_stack.last().unwrap_or(&false)
&& bytes.get(i + "print!".len()) == Some(&b'(')
{
violations += 1;
i += "print!".len();
continue;
}
i += 1;
}
violations
}
fn rest_starts_with(bytes: &[u8], at: usize, needle: &str) -> bool {
let n = needle.as_bytes();
if at + n.len() > bytes.len() {
return false;
}
&bytes[at..at + n.len()] == n
}