use std::fs;
use std::path::{Path, PathBuf};
fn production_lines(content: &str) -> Vec<(usize, String)> {
let mut out = Vec::new();
let mut depth: usize = 0;
let mut test_mod_depths: Vec<usize> = Vec::new();
let mut prev_line_marks_test_mod = false;
for (i, raw_line) in content.lines().enumerate() {
let line = raw_line;
let trimmed = line.trim_start();
let attr_marks_test = trimmed.starts_with("#[cfg(test)]")
|| trimmed.starts_with("#[cfg(any(test")
|| trimmed.starts_with("#[cfg(all(test");
if test_mod_depths.is_empty() && !trimmed.starts_with("//") {
out.push((i + 1, line.to_string()));
}
for (col, ch) in line.char_indices() {
if ch == '/' && line.as_bytes().get(col + 1) == Some(&b'/') {
break;
}
match ch {
'{' => {
depth += 1;
if prev_line_marks_test_mod
&& (trimmed.starts_with("mod ") || trimmed.contains(" mod "))
{
test_mod_depths.push(depth);
prev_line_marks_test_mod = false;
}
}
'}' => {
if let Some(&top) = test_mod_depths.last() {
if depth == top {
test_mod_depths.pop();
}
}
depth = depth.saturating_sub(1);
}
_ => {}
}
}
if attr_marks_test {
if trimmed.contains("mod ") && trimmed.contains('{') {
test_mod_depths.push(depth);
} else {
prev_line_marks_test_mod = true;
}
} else if !attr_marks_test
&& !trimmed.is_empty()
&& !trimmed.starts_with("//")
&& !trimmed.starts_with("#[")
{
prev_line_marks_test_mod = false;
}
}
out
}
fn collect_rs_files(dir: &Path) -> Vec<(PathBuf, String)> {
let mut out = Vec::new();
let entries = match fs::read_dir(dir) {
Ok(e) => e,
Err(_) => return out,
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
out.extend(collect_rs_files(&path));
} else if path.extension().is_some_and(|e| e == "rs") {
if let Ok(content) = fs::read_to_string(&path) {
out.push((path, content));
}
}
}
out
}
fn use_lines(content: &str) -> Vec<(usize, String)> {
let mut out = Vec::new();
let mut depth: usize = 0; let mut test_mod_depths: Vec<usize> = Vec::new();
let mut prev_line_marks_test_mod = false;
for (i, raw_line) in content.lines().enumerate() {
let line = raw_line;
let trimmed = line.trim_start();
let attr_marks_test = trimmed.starts_with("#[cfg(test)]")
|| trimmed.starts_with("#[cfg(any(test")
|| trimmed.starts_with("#[cfg(all(test");
if !test_mod_depths.is_empty() {
}
if trimmed.starts_with("use crate::") || trimmed.starts_with("pub use crate::") {
if test_mod_depths.is_empty() {
out.push((i + 1, line.to_string()));
}
}
for (col, ch) in line.char_indices() {
if ch == '/' && line.as_bytes().get(col + 1) == Some(&b'/') {
break;
}
match ch {
'{' => {
depth += 1;
if prev_line_marks_test_mod
&& (trimmed.starts_with("mod ") || trimmed.contains(" mod "))
{
test_mod_depths.push(depth);
prev_line_marks_test_mod = false;
}
}
'}' => {
if let Some(&top) = test_mod_depths.last() {
if depth == top {
test_mod_depths.pop();
}
}
depth = depth.saturating_sub(1);
}
_ => {}
}
}
if attr_marks_test {
if trimmed.contains("mod ") && trimmed.contains('{') {
test_mod_depths.push(depth); } else {
prev_line_marks_test_mod = true;
}
} else if !attr_marks_test
&& !trimmed.is_empty()
&& !trimmed.starts_with("//")
&& !trimmed.starts_with("#[")
{
prev_line_marks_test_mod = false;
}
}
out
}
fn src_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src")
}
#[test]
fn no_infra_to_app_imports() {
let infra_dir = src_root().join("infra");
let exempt_substrings = ["crate::app::config::ConfigError"];
let mut violations = Vec::new();
for (path, content) in collect_rs_files(&infra_dir) {
for (lineno, line) in use_lines(&content) {
if !line.contains("crate::app") {
continue;
}
if exempt_substrings.iter().any(|s| line.contains(s)) {
continue;
}
violations.push(format!(
"{}:{lineno}: {}",
path.strip_prefix(src_root().parent().unwrap())
.unwrap_or(&path)
.display(),
line.trim()
));
}
}
assert!(
violations.is_empty(),
"infra/ must not import from app/ (other than the exempt type-only \
imports). Found:\n {}",
violations.join("\n ")
);
}
#[test]
fn no_domain_to_anyone_else_imports() {
let domain_dir = src_root().join("domain");
let forbidden = ["crate::app", "crate::infra", "crate::cmd", "crate::util"];
let mut violations = Vec::new();
for (path, content) in collect_rs_files(&domain_dir) {
for (lineno, line) in use_lines(&content) {
for needle in forbidden {
if line.contains(needle) {
violations.push(format!(
"{}:{lineno}: {}",
path.strip_prefix(src_root().parent().unwrap())
.unwrap_or(&path)
.display(),
line.trim()
));
}
}
}
}
assert!(
violations.is_empty(),
"domain/ must not import from app/infra/cmd/util. Found:\n {}",
violations.join("\n ")
);
}
#[test]
fn no_cmd_to_domain_behavior_imports() {
let cmd_dir = src_root().join("cmd");
let forbidden = [
"crate::domain::expand",
"crate::domain::hook",
"crate::domain::shell::export_script",
];
let mut violations = Vec::new();
for (path, content) in collect_rs_files(&cmd_dir) {
for (lineno, line) in use_lines(&content) {
for needle in forbidden {
if line.contains(needle) {
violations.push(format!(
"{}:{lineno}: {}",
path.strip_prefix(src_root().parent().unwrap())
.unwrap_or(&path)
.display(),
line.trim()
));
}
}
}
}
assert!(
violations.is_empty(),
"cmd/ must not import domain behavior modules (expand/hook). \
Use the app/* usecase wrappers instead. Found:\n {}",
violations.join("\n ")
);
}
#[test]
fn no_filesystem_calls_in_app_layer() {
let app_dir = src_root().join("app");
let needles = [
"std::fs::write",
"std::fs::read",
"std::fs::rename",
"std::fs::remove_file",
"std::fs::create_dir",
"std::fs::OpenOptions",
"std::fs::File::open",
"std::fs::File::create",
"std::fs::canonicalize",
"std::fs::symlink_metadata",
];
let mut violations = Vec::new();
for (path, content) in collect_rs_files(&app_dir) {
for (lineno, line) in production_lines(&content) {
for needle in needles {
if line.contains(needle) {
violations.push(format!(
"{}:{lineno}: {}",
path.strip_prefix(src_root().parent().unwrap())
.unwrap_or(&path)
.display(),
line.trim()
));
}
}
}
}
assert!(
violations.is_empty(),
"app/ must not perform file-system calls in production code. \
Move the I/O into infra/config_store.rs (or another infra/* \
module). Found:\n {}",
violations.join("\n ")
);
}