use std::fs;
use std::path::{Path, PathBuf};
use walkdir::WalkDir;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ZeroStubFinding {
pub file: PathBuf,
pub line: usize,
pub kind: StubKind,
pub snippet: String,
pub fix: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum StubKind {
TodoMacro,
UnimplementedMacro,
UnreachableMacro,
TodoComment,
FixmeComment,
XxxComment,
HackComment,
StubComment,
AllowDeadCodeAttribute,
}
impl StubKind {
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::TodoMacro => "todo_macro",
Self::UnimplementedMacro => "unimplemented_macro",
Self::UnreachableMacro => "unreachable_macro",
Self::TodoComment => "todo_comment",
Self::FixmeComment => "fixme_comment",
Self::XxxComment => "xxx_comment",
Self::HackComment => "hack_comment",
Self::StubComment => "stub_comment",
Self::AllowDeadCodeAttribute => "allow_dead_code",
}
}
#[must_use]
pub const fn fix_hint(self) -> &'static str {
match self {
Self::TodoMacro => {
"Fix: implement the function body instead of calling `todo!()`. \
If the function cannot be implemented yet, delete it and delete \
every caller until a real implementation lands."
}
Self::UnimplementedMacro => {
"Fix: implement the function. `unimplemented!()` hides a missing \
contract; ship the real body or remove the call path entirely."
}
Self::UnreachableMacro => {
"Fix: restructure the code so the branch is type-level impossible \
(match on a non-exhaustive enum, use a Result, etc). \
`unreachable!()` silently converts bugs into panics."
}
Self::TodoComment
| Self::FixmeComment
| Self::XxxComment
| Self::HackComment
| Self::StubComment => {
"Fix: either make the change the comment describes in the same \
commit, or file a tracked issue and delete the comment. \
Undelivered intent comments rot into lies."
}
Self::AllowDeadCodeAttribute => {
"Fix: either wire the unused item into a real caller or delete it. \
Dead code is either a bug that hasn't been caught yet or a \
lie about what the crate supports."
}
}
}
}
impl std::fmt::Display for ZeroStubFinding {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}:{}: {}: {}. {}",
self.file.display(),
self.line,
self.kind.name(),
self.snippet.trim(),
self.fix
)
}
}
#[derive(Debug, Clone)]
pub struct ZeroStubsConfig {
pub roots: Vec<PathBuf>,
pub skip_dirs: Vec<String>,
pub allowlist: Vec<PathBuf>,
}
impl ZeroStubsConfig {
#[must_use]
#[inline]
pub fn with_root(root: impl Into<PathBuf>) -> Self {
Self {
roots: vec![root.into()],
skip_dirs: default_skip_dirs(),
allowlist: Vec::new(),
}
}
}
fn default_skip_dirs() -> Vec<String> {
[
"target",
".git",
"coordination",
"docs",
"mutants.out",
"mutants.out.old",
"corpus",
"corpus_staging",
]
.into_iter()
.map(String::from)
.collect()
}
#[inline]
pub fn scan(config: &ZeroStubsConfig) -> Result<Vec<ZeroStubFinding>, String> {
let mut findings = Vec::new();
for root in &config.roots {
scan_root(root, config, &mut findings)?;
}
findings.sort_by(|a, b| {
a.file
.cmp(&b.file)
.then_with(|| a.line.cmp(&b.line))
.then_with(|| a.kind.cmp(&b.kind))
});
Ok(findings)
}
fn scan_root(
root: &Path,
config: &ZeroStubsConfig,
findings: &mut Vec<ZeroStubFinding>,
) -> Result<(), String> {
if !root.exists() {
return Err(format!(
"zero-stubs gate root does not exist: {}. Fix: pass an existing directory.",
root.display()
));
}
let walker = WalkDir::new(root).into_iter().filter_entry(|entry| {
if entry.depth() == 0 {
return true;
}
let name = entry.file_name().to_string_lossy();
!config.skip_dirs.iter().any(|skip| skip == name.as_ref())
});
for entry in walker {
let entry = entry.map_err(|error| {
format!(
"zero-stubs gate walker error under {}: {error}. Fix: restore read permissions.",
root.display()
)
})?;
if !entry.file_type().is_file() {
continue;
}
let path = entry.path();
if path.extension().and_then(|ext| ext.to_str()) != Some("rs") {
continue;
}
if config
.allowlist
.iter()
.any(|allow| path.ends_with(allow) || path == allow)
{
continue;
}
scan_file(root, path, findings)?;
}
Ok(())
}
fn scan_file(root: &Path, path: &Path, findings: &mut Vec<ZeroStubFinding>) -> Result<(), String> {
let source = fs::read_to_string(path).map_err(|error| {
format!(
"zero-stubs gate failed to read {}: {error}. Fix: restore read permissions.",
path.display()
)
})?;
let rel = path.strip_prefix(root).unwrap_or(path).to_path_buf();
for (line_number, line) in source.lines().enumerate() {
let line_number = line_number + 1;
if let Some(kind) = detect_comment_marker(line) {
findings.push(ZeroStubFinding {
file: rel.clone(),
line: line_number,
kind,
snippet: truncate(line, 160),
fix: kind.fix_hint().to_string(),
});
}
for (marker, kind) in MACRO_MARKERS {
if contains_macro(line, marker) && !is_in_test_module_line(&rel, &source, line_number) {
findings.push(ZeroStubFinding {
file: rel.clone(),
line: line_number,
kind: *kind,
snippet: truncate(line, 160),
fix: kind.fix_hint().to_string(),
});
}
}
}
for (line_number, line) in source.lines().enumerate() {
let line_number = line_number + 1;
let trimmed = line.trim_start();
if trimmed.starts_with("#[allow(dead_code)]") || trimmed.starts_with("#![allow(dead_code)]")
{
findings.push(ZeroStubFinding {
file: rel.clone(),
line: line_number,
kind: StubKind::AllowDeadCodeAttribute,
snippet: truncate(line, 160),
fix: StubKind::AllowDeadCodeAttribute.fix_hint().to_string(),
});
}
}
Ok(())
}
fn truncate(input: &str, max: usize) -> String {
let trimmed = input.trim();
if trimmed.len() <= max {
trimmed.to_string()
} else {
let mut out = trimmed[..max - 1].to_string();
out.push('…');
out
}
}
fn detect_comment_marker(line: &str) -> Option<StubKind> {
let trimmed = line.trim_start();
if !trimmed.starts_with("//") {
return None;
}
let body = trimmed
.trim_start_matches('/')
.trim_start_matches('!')
.trim_start();
let upper: String = body
.chars()
.take(8)
.collect::<String>()
.to_ascii_uppercase();
if upper.starts_with("TODO") {
return Some(StubKind::TodoComment);
}
if upper.starts_with("FIXME") {
return Some(StubKind::FixmeComment);
}
if upper.starts_with("XXX") {
return Some(StubKind::XxxComment);
}
if upper.starts_with("HACK") {
return Some(StubKind::HackComment);
}
if upper.starts_with("STUB") {
return Some(StubKind::StubComment);
}
None
}
const MACRO_MARKERS: &[(&str, StubKind)] = &[
("todo!", StubKind::TodoMacro),
("unimplemented!", StubKind::UnimplementedMacro),
("unreachable!", StubKind::UnreachableMacro),
];
fn contains_macro(line: &str, marker: &str) -> bool {
let mut remaining = line;
while let Some(idx) = remaining.find(marker) {
let preceded_by_ident = idx > 0
&& (remaining.as_bytes()[idx - 1].is_ascii_alphanumeric()
|| remaining.as_bytes()[idx - 1] == b'_');
let after = &remaining[idx + marker.len()..];
let followed_by_paren =
after.starts_with('(') || after.starts_with(' ') && after.trim_start().starts_with('(');
if !preceded_by_ident && followed_by_paren {
return true;
}
remaining = &remaining[idx + marker.len()..];
}
false
}
fn is_in_test_module_line(rel: &Path, source: &str, line_number: usize) -> bool {
if rel
.components()
.any(|component| component.as_os_str() == "tests")
{
return true;
}
if rel
.file_stem()
.and_then(|stem| stem.to_str())
.is_some_and(|stem| stem == "tests" || stem.ends_with("_tests"))
{
return true;
}
let mut inside = false;
let mut depth: i32 = 0;
for (index, line) in source.lines().enumerate() {
if index + 1 > line_number {
break;
}
let trimmed = line.trim_start();
if !inside && (trimmed.starts_with("#[cfg(test)]") || trimmed.starts_with("#[cfg(all(test"))
{
inside = source
.lines()
.nth(index + 1)
.map(|next| next.trim_start().starts_with("mod "))
.unwrap_or(false);
if inside {
depth = 0;
}
}
if inside {
depth += i32::try_from(line.matches('{').count()).unwrap_or(0);
depth -= i32::try_from(line.matches('}').count()).unwrap_or(0);
if depth <= 0 && index + 1 > line_number {
return true;
}
if depth <= 0 {
inside = false;
}
}
}
inside
}
pub struct ZeroStubsEnforcer;
impl crate::enforce::EnforceGate for ZeroStubsEnforcer {
fn id(&self) -> &'static str {
"zero_stubs"
}
fn name(&self) -> &'static str {
"zero_stubs"
}
fn run(&self, ctx: &crate::enforce::EnforceCtx<'_>) -> Vec<crate::enforce::Finding> {
let config = ZeroStubsConfig::with_root(ctx.workspace_root);
match scan(&config) {
Ok(findings) => crate::enforce::finding_result(
self.id(),
findings
.into_iter()
.map(|finding| finding.to_string())
.collect(),
),
Err(error) => vec![crate::enforce::aggregate_finding(self.id(), vec![error])],
}
}
}
pub const REGISTERED: ZeroStubsEnforcer = ZeroStubsEnforcer;
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::TempDir;
fn write_file(dir: &TempDir, rel: &str, content: &str) -> PathBuf {
let path = dir.path().join(rel);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).unwrap();
}
let mut file = std::fs::File::create(&path).unwrap();
file.write_all(content.as_bytes()).unwrap();
path
}
fn run(dir: &TempDir) -> Vec<ZeroStubFinding> {
scan(&ZeroStubsConfig::with_root(dir.path())).expect("scan must not fail on clean tmpdir")
}
#[test]
fn clean_tree_produces_no_findings() {
let dir = TempDir::new().unwrap();
write_file(&dir, "src/lib.rs", "pub fn answer() -> u32 { 42 }\n");
let findings = run(&dir);
assert!(
findings.is_empty(),
"a clean file must produce zero findings, got: {findings:?}"
);
}
#[test]
fn detects_todo_macro_in_production_code() {
let dir = TempDir::new().unwrap();
write_file(&dir, "src/lib.rs", "pub fn broken() -> u32 { todo!() }\n");
let findings = run(&dir);
assert_eq!(findings.len(), 1, "{findings:?}");
assert_eq!(findings[0].kind, StubKind::TodoMacro);
assert!(findings[0].fix.starts_with("Fix:"));
}
#[test]
fn detects_unimplemented_and_unreachable() {
let dir = TempDir::new().unwrap();
write_file(&dir, "src/a.rs", "pub fn a() -> u32 { unimplemented!() }\n");
write_file(
&dir,
"src/b.rs",
"pub fn b() { match 0u32 { _ => unreachable!() } }\n",
);
let findings = run(&dir);
let kinds: Vec<_> = findings.iter().map(|finding| finding.kind).collect();
assert!(kinds.contains(&StubKind::UnimplementedMacro));
assert!(kinds.contains(&StubKind::UnreachableMacro));
}
#[test]
fn todo_comment_is_detected_case_insensitively() {
let dir = TempDir::new().unwrap();
write_file(
&dir,
"src/lib.rs",
"// TODO: wire this up\n// todo: lowercase also\npub fn live() {}\n",
);
let findings = run(&dir);
let kinds: Vec<_> = findings.iter().map(|finding| finding.kind).collect();
assert!(
kinds
.iter()
.filter(|kind| **kind == StubKind::TodoComment)
.count()
>= 2,
"both TODO comments should fire: {findings:?}"
);
}
#[test]
fn fixme_xxx_hack_stub_comments_each_have_their_own_kind() {
let dir = TempDir::new().unwrap();
write_file(
&dir,
"src/lib.rs",
"// FIXME one\n// XXX two\n// HACK three\n// STUB four\n",
);
let findings = run(&dir);
let kinds: std::collections::BTreeSet<_> =
findings.iter().map(|finding| finding.kind).collect();
for expected in [
StubKind::FixmeComment,
StubKind::XxxComment,
StubKind::HackComment,
StubKind::StubComment,
] {
assert!(kinds.contains(&expected), "missing kind: {expected:?}");
}
}
#[test]
fn allow_dead_code_attribute_is_flagged() {
let dir = TempDir::new().unwrap();
write_file(
&dir,
"src/lib.rs",
"#[allow(dead_code)]\nfn hidden() -> u32 { 1 }\n",
);
let findings = run(&dir);
assert!(
findings
.iter()
.any(|finding| finding.kind == StubKind::AllowDeadCodeAttribute),
"expected AllowDeadCodeAttribute finding in {findings:?}"
);
}
#[test]
fn macro_in_test_module_path_is_ignored() {
let dir = TempDir::new().unwrap();
write_file(
&dir,
"src/tests/unit/broken.rs",
"#[test] fn t() { todo!() }\n",
);
let findings = run(&dir);
assert!(
!findings
.iter()
.any(|finding| finding.kind == StubKind::TodoMacro),
"in-test todo!() should be ignored: {findings:?}"
);
}
#[test]
fn substring_match_does_not_false_positive_on_suffix_identifiers() {
let dir = TempDir::new().unwrap();
write_file(
&dir,
"src/lib.rs",
"fn f(custom_todo: u32) -> u32 { custom_todo }\nfn g() { my_unreachable!(); }\n",
);
let findings = run(&dir);
assert!(
findings.iter().all(|finding| matches!(
finding.kind,
StubKind::TodoComment
| StubKind::FixmeComment
| StubKind::XxxComment
| StubKind::HackComment
| StubKind::StubComment
| StubKind::AllowDeadCodeAttribute
)),
"false-positive on identifier suffix: {findings:?}"
);
}
#[test]
fn findings_are_deterministically_sorted() {
let dir = TempDir::new().unwrap();
write_file(&dir, "src/z.rs", "// TODO last\n");
write_file(&dir, "src/a.rs", "// TODO first\n");
write_file(&dir, "src/m.rs", "// TODO middle\n");
let findings = run(&dir);
let paths: Vec<_> = findings
.iter()
.map(|finding| finding.file.to_string_lossy().to_string())
.collect();
let mut sorted = paths.clone();
sorted.sort();
assert_eq!(paths, sorted, "findings must be sorted by path: {paths:?}");
}
#[test]
fn display_format_is_actionable() {
let finding = ZeroStubFinding {
file: PathBuf::from("src/a.rs"),
line: 42,
kind: StubKind::TodoMacro,
snippet: "todo!()".to_string(),
fix: StubKind::TodoMacro.fix_hint().to_string(),
};
let rendered = format!("{finding}");
assert!(rendered.contains("src/a.rs"), "{rendered}");
assert!(rendered.contains("42"), "{rendered}");
assert!(rendered.contains("todo_macro"), "{rendered}");
assert!(rendered.contains("Fix:"), "{rendered}");
}
#[test]
fn skip_dirs_default_excludes_target_and_docs() {
let dir = TempDir::new().unwrap();
write_file(
&dir,
"target/poison.rs",
"// TODO target must never appear\n",
);
write_file(&dir, "docs/poison.rs", "// TODO docs must never appear\n");
write_file(&dir, "src/good.rs", "pub fn ok() {}\n");
let findings = run(&dir);
assert!(
findings.is_empty(),
"default skip dirs should exclude everything in this scenario: {findings:?}"
);
}
#[test]
fn allowlist_suppresses_specific_files() {
let dir = TempDir::new().unwrap();
write_file(&dir, "src/ok.rs", "pub fn ok() {}\n");
write_file(&dir, "src/waived.rs", "// TODO tracked elsewhere\n");
let config = ZeroStubsConfig {
roots: vec![dir.path().to_path_buf()],
skip_dirs: default_skip_dirs(),
allowlist: vec![PathBuf::from("src/waived.rs")],
};
let findings = scan(&config).unwrap();
assert!(
findings.is_empty(),
"allowlisted file must not produce findings: {findings:?}"
);
}
#[test]
fn missing_root_returns_actionable_error() {
let config = ZeroStubsConfig::with_root("/definitely/does/not/exist/vyre-zero-stubs");
let error = scan(&config).unwrap_err();
assert!(
error.starts_with("zero-stubs gate root does not exist"),
"{error}"
);
assert!(error.contains("Fix:"), "{error}");
}
#[test]
fn stub_kind_names_are_unique() {
use std::collections::BTreeSet;
let names: BTreeSet<_> = [
StubKind::TodoMacro,
StubKind::UnimplementedMacro,
StubKind::UnreachableMacro,
StubKind::TodoComment,
StubKind::FixmeComment,
StubKind::XxxComment,
StubKind::HackComment,
StubKind::StubComment,
StubKind::AllowDeadCodeAttribute,
]
.into_iter()
.map(|kind| kind.name())
.collect();
assert_eq!(names.len(), 9, "{names:?}");
}
}