use crate::domain::finding::Finding;
use crate::domain::rule_id::RuleId;
use crate::gates::{GateCtx, GateResult, Violation, read_text};
pub const CITES: &[RuleId] = &[RuleId::CellCarriesOneReference];
fn cell_has_extra_references(line: &str) -> bool {
let fields: Vec<&str> = line.split('|').collect();
if fields.len() < 3 {
return false;
}
fields[1..fields.len() - 1]
.iter()
.any(|cell| cell.matches("](").count() > 1)
}
pub fn run(ctx: &GateCtx, files: &[String]) -> GateResult {
let mut violations = Vec::new();
for file in files {
for (number, line) in read_text(ctx, file)?.lines().enumerate() {
if line.starts_with('|') && cell_has_extra_references(line) {
violations.push(Violation::Finding(Finding::on_line(
RuleId::CellCarriesOneReference,
file,
number + 1,
"",
)));
}
}
}
Ok(violations)
}
#[cfg(test)]
mod tests {
use super::*;
fn run_on(text: &str) -> Vec<String> {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("comparison.md"), text).unwrap();
let ctx = GateCtx::new(dir.path().to_str().unwrap());
run(&ctx, &["comparison.md".to_string()])
.unwrap()
.iter()
.map(ToString::to_string)
.collect()
}
#[test]
fn accepts_one_reference_per_cell() {
assert!(run_on("| [Case](#case) | Subject |\n| Runs | ✅ yes |\n").is_empty());
}
#[test]
fn rejects_two_references_in_one_cell() {
let out = run_on("| Runs [a](#a) [b](#b) | ✅ yes |\n");
assert_eq!(
out,
vec!["FAIL comparison-docs:a-cell-carries-one-reference comparison.md:1".to_string()]
);
}
}