use std::fs;
use std::path::{Path, PathBuf};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum Rule {
DurationLiteral,
BackupInfix,
SidecarName,
TimeUnitNumber,
EnvRead,
ProcessSpawn,
}
const SIDECAR_NAMES: &[&str] = &[
".atomwrite.journal",
".atomwrite.lock",
".atomwrite_keep_wal",
".atomwrite_no_wal",
".atomwrite-doctor-probe",
"tokens.ndjson",
];
const TIME_UNIT_NUMBERS: &[&str] = &["86400", "86_400", "604800", "604_800", "3600"];
const DURATION_CTORS: &[&str] = &[
"Duration::from_secs(",
"Duration::from_millis(",
"Duration::from_micros(",
"Duration::from_nanos(",
];
const ALLOWED: &[(&str, Rule, &str, &str)] = &[
(
"build.rs",
Rule::ProcessSpawn,
"Command::new(\"git\")",
"the build script embeds the commit hash; git runs at compile time only",
),
(
"build.rs",
Rule::EnvRead,
"env::var_os(\"CARGO_MANIFEST_DIR\")",
"cargo passes the manifest directory to every build script",
),
(
"build.rs",
Rule::EnvRead,
"env::var(\"TARGET\")",
"cargo passes the target triple to every build script",
),
(
"xtask/src/main.rs",
Rule::ProcessSpawn,
"Command::new(\"cargo\")",
"the xtask runner shells out to cargo by design",
),
];
fn src_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("src")
}
fn collect_rs_files(dir: &Path, out: &mut Vec<PathBuf>) {
let entries = fs::read_dir(dir).unwrap_or_else(|e| panic!("read_dir {}: {e}", dir.display()));
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
collect_rs_files(&path, out);
} else if path.extension().is_some_and(|ext| ext == "rs") {
out.push(path);
}
}
}
fn is_skipped_file(path: &Path) -> bool {
let name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
name.starts_with("constants") || name.contains("tests")
}
fn indent_of(line: &str) -> usize {
line.len() - line.trim_start().len()
}
fn production_lines(text: &str) -> Vec<(usize, &str)> {
let lines: Vec<&str> = text.lines().collect();
let mut out = Vec::new();
let mut i = 0;
while i < lines.len() {
let line = lines[i];
let trimmed = line.trim();
if trimmed == "#[cfg(test)]" {
let indent = indent_of(line);
i += 1;
while i < lines.len()
&& (lines[i].trim().starts_with("#[") || lines[i].trim().starts_with("//"))
{
i += 1;
}
let Some(first) = lines.get(i) else { break };
let first = first.trim_end();
if first.ends_with(';') || (first.ends_with('}') && !first.ends_with("{}")) {
i += 1;
continue;
}
i += 1;
while i < lines.len() {
let closes =
indent_of(lines[i]) == indent && lines[i].trim_start().starts_with('}');
i += 1;
if closes {
break;
}
}
continue;
}
if !trimmed.starts_with("//") && !trimmed.contains("help = \"") {
out.push((i + 1, line));
}
i += 1;
}
out
}
fn duration_literal_in(text: &str) -> bool {
DURATION_CTORS.iter().any(|ctor| {
text.match_indices(ctor).any(|(at, _)| {
text[at + ctor.len()..]
.trim_start()
.chars()
.next()
.is_some_and(|c| c.is_ascii_digit())
})
})
}
fn violations_in(line: &str) -> Vec<Rule> {
let mut found = Vec::new();
if duration_literal_in(line) {
found.push(Rule::DurationLiteral);
}
if line.contains(".bak.") {
found.push(Rule::BackupInfix);
}
if SIDECAR_NAMES.iter().any(|name| line.contains(name)) {
found.push(Rule::SidecarName);
}
if TIME_UNIT_NUMBERS.iter().any(|n| contains_number(line, n)) {
found.push(Rule::TimeUnitNumber);
}
if line.contains("env::var") {
found.push(Rule::EnvRead);
}
if line.contains("Command::new") {
found.push(Rule::ProcessSpawn);
}
found
}
fn contains_number(line: &str, number: &str) -> bool {
line.match_indices(number).any(|(at, _)| {
let before = line[..at].chars().next_back();
let after = line[at + number.len()..].chars().next();
let is_part = |c: Option<char>| c.is_some_and(|c| c.is_ascii_alphanumeric() || c == '_');
!is_part(before) && !is_part(after)
})
}
fn is_allowed(file: &str, rule: Rule, line: &str) -> bool {
ALLOWED
.iter()
.any(|(suffix, r, needle, _)| file.ends_with(suffix) && *r == rule && line.contains(needle))
}
fn scan_sources() -> Vec<String> {
let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
let mut files = Vec::new();
collect_rs_files(&src_root(), &mut files);
let build_script = manifest.join("build.rs");
if build_script.is_file() {
files.push(build_script);
}
collect_rs_files(&manifest.join("xtask").join("src"), &mut files);
files.sort();
let mut report = Vec::new();
for path in files.iter().filter(|p| !is_skipped_file(p)) {
let text = fs::read_to_string(path).unwrap_or_else(|e| panic!("{}: {e}", path.display()));
let rel = path
.strip_prefix(manifest)
.unwrap_or(path)
.to_string_lossy()
.replace('\\', "/");
for (number, line) in production_lines(&text) {
for rule in violations_in(line) {
if !is_allowed(&rel, rule, line) {
report.push(format!("{rel}:{number}: {rule:?}: {}", line.trim()));
}
}
}
}
report
}
#[test]
fn production_code_has_no_hardcoded_literals() {
let report = scan_sources();
assert!(
report.is_empty(),
"move these literals to src/constants.rs (or list a reasoned exception in ALLOWED):\n{}",
report.join("\n")
);
}
#[test]
fn every_allowed_exception_still_matches_a_real_line() {
let root = src_root();
for (suffix, rule, needle, reason) in ALLOWED {
assert!(!reason.is_empty(), "exception for {suffix} needs a reason");
let path = root.parent().unwrap_or(&root).join(suffix);
let text = fs::read_to_string(&path).unwrap_or_else(|e| panic!("{}: {e}", path.display()));
let hit = production_lines(&text)
.into_iter()
.any(|(_, line)| line.contains(needle) && violations_in(line).contains(rule));
assert!(
hit,
"stale exception: {suffix} no longer has a {rule:?} line containing {needle:?}; remove it"
);
}
}
#[test]
fn guard_detects_each_rule_on_a_sample() {
assert!(violations_in("let d = Duration::from_millis(250);").contains(&Rule::DurationLiteral));
assert!(
violations_in("let d = std::time::Duration::from_secs( 5 );")
.contains(&Rule::DurationLiteral)
);
assert!(!violations_in("let d = Duration::from_millis(ms);").contains(&Rule::DurationLiteral));
assert!(
!violations_in("let d = Duration::from_millis(C::POLL_MS);")
.contains(&Rule::DurationLiteral)
);
assert!(violations_in("format!(\"{name}.bak.\")").contains(&Rule::BackupInfix));
assert!(violations_in("dir.join(\".atomwrite.lock\")").contains(&Rule::SidecarName));
assert!(violations_in("let day = secs / 86400;").contains(&Rule::TimeUnitNumber));
assert!(!violations_in("let n = 1_086_400_000;").contains(&Rule::TimeUnitNumber));
assert!(violations_in("let v = std::env::var(\"X\");").contains(&Rule::EnvRead));
assert!(violations_in("let v = env::var_os(\"X\");").contains(&Rule::EnvRead));
assert!(violations_in("std::process::Command::new(\"ls\")").contains(&Rule::ProcessSpawn));
assert!(violations_in("let t = env!(\"TARGET\");").is_empty());
}
#[test]
fn guard_skips_test_items_and_comments() {
let text = "\
fn prod() {}
// Duration::from_secs(5)
#[cfg(test)]
mod tests {
fn t() {
let _ = Duration::from_secs(5);
}
}
fn after() {}
#[cfg(test)]
fn helper() {
let _ = \".bak.\";
}
#[cfg(test)]
include!(\"x_tests.inc.rs\");
fn last() {}
";
let kept: Vec<&str> = production_lines(text).into_iter().map(|(_, l)| l).collect();
assert_eq!(kept, ["fn prod() {}", "fn after() {}", "fn last() {}"]);
}
#[test]
fn product_ships_no_script_or_ci_files() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
assert!(!root.join(".github").exists(), ".github must not exist");
let mut files = Vec::new();
collect_all_files(root, &mut files);
let scripts: Vec<String> = files
.iter()
.filter(|p| p.extension().is_some_and(|e| e == "sh"))
.map(|p| p.display().to_string())
.collect();
assert!(scripts.is_empty(), "no .sh file may exist: {scripts:?}");
}
fn collect_all_files(dir: &Path, out: &mut Vec<PathBuf>) {
let Ok(entries) = fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
let name = entry.file_name();
let name = name.to_string_lossy();
if path.is_dir() {
if name == "target" || name == ".git" || name.starts_with('.') && name != ".github" {
continue;
}
collect_all_files(&path, out);
} else {
out.push(path);
}
}
}