use std::collections::BTreeSet;
use crate::domain::finding::Finding;
use crate::domain::rule_id::RuleId;
use crate::gates::paths::ki_records;
use crate::gates::{GateCtx, GateError, GateResult, Violation, walk_files};
pub const CITES: &[RuleId] = &[RuleId::SuppressionNamesItsCase];
const CASE: RuleId = RuleId::SuppressionNamesItsCase;
fn looks_binary(bytes: &[u8]) -> bool {
bytes.iter().take(4096).any(|&b| b == 0)
}
const fn on_a_boundary(text: &str, index: usize) -> bool {
index == 0
|| !text.as_bytes()[index - 1].is_ascii_alphanumeric()
&& text.as_bytes()[index - 1] != b'-'
&& text.as_bytes()[index - 1] != b'_'
}
fn cited_cases(line: &str) -> impl Iterator<Item = String> + '_ {
line.match_indices("KI-")
.filter(|(index, _)| on_a_boundary(line, *index))
.filter_map(|(index, _)| {
let rest = &line[index + 3..];
let slug: String = rest
.chars()
.take_while(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || *c == '-')
.collect();
let closes = rest[slug.len()..]
.chars()
.next()
.is_none_or(|c| !c.is_ascii_alphanumeric() && c != '_');
(!slug.is_empty() && closes).then(|| format!("KI-{slug}"))
})
}
struct Citation {
case: String,
file: String,
number: usize,
}
fn citations(ctx: &GateCtx) -> Result<Vec<Citation>, GateError> {
let mut found = Vec::new();
for file in walk_files(ctx) {
let bytes =
std::fs::read(ctx.path(&file)).map_err(|source| GateError::io(file.clone(), source))?;
if looks_binary(&bytes) {
continue;
}
let text = String::from_utf8_lossy(&bytes);
let name = file.as_str().trim_start_matches("./").to_string();
for (index, line) in text.lines().enumerate() {
found.extend(cited_cases(line).map(|case| Citation {
case,
file: name.clone(),
number: index + 1,
}));
}
}
Ok(found)
}
pub fn run(ctx: &GateCtx, args: &[String]) -> GateResult {
let known: BTreeSet<String> = ki_records(ctx, args)?
.iter()
.filter_map(|record| {
record
.file_name()
.map(|name| name.trim_end_matches(".md").to_string())
})
.collect();
Ok(citations(ctx)?
.into_iter()
.filter(|citation| !known.contains(&citation.case))
.map(|citation| {
Violation::Finding(Finding::on_line(
CASE,
&citation.file,
citation.number,
format!("{} resolves to no record", citation.case),
))
})
.collect())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::domain::path_filter::{Layer, PathFilter, Pattern};
fn excluding(dir: &tempfile::TempDir, glob: &str) -> GateCtx {
let filter =
PathFilter::build(Vec::new(), vec![Pattern::new(glob, Layer::Project)]).unwrap();
GateCtx::with_filter(dir.path().to_str().unwrap(), filter)
}
fn tree(files: &[(&str, &str)]) -> tempfile::TempDir {
let dir = tempfile::tempdir().unwrap();
for (path, text) in files {
let full = dir.path().join(path);
std::fs::create_dir_all(full.parent().unwrap()).unwrap();
std::fs::write(full, text).unwrap();
}
dir
}
fn run_in(dir: &tempfile::TempDir) -> Vec<String> {
run(&GateCtx::new(dir.path().to_str().unwrap()), &[])
.unwrap()
.iter()
.map(ToString::to_string)
.collect()
}
const RECORD: (&str, &str) = (
"_docs/reference/known-issues/KI-vendor-replays.md",
"# Vendor replays\n",
);
#[test]
fn an_absent_record_fails_whatever_file_cites_it() {
for name in [
"src/client.rs",
"deploy/pipeline.yaml",
"notes.txt",
"scripts/publish",
"Makefile",
] {
let dir = tree(&[(name, "mask for KI-vendor-replays\n")]);
let out = run_in(&dir);
assert_eq!(out.len(), 1, "{name} reported {out:?}");
assert_eq!(
out[0],
format!(
"FAIL spec-to-code:a-suppression-names-its-case {name}:1: \
KI-vendor-replays resolves to no record"
)
);
}
}
#[test]
fn a_repository_keeping_no_records_still_fails_a_citation() {
let dir = tree(&[("src/client.rs", "// KI-vendor-replays\n")]);
assert!(!dir.path().join("_docs/reference/known-issues").exists());
assert_eq!(run_in(&dir).len(), 1);
}
#[test]
fn a_citation_whose_record_exists_passes() {
let dir = tree(&[("src/client.rs", "// KI-vendor-replays\n"), RECORD]);
assert!(run_in(&dir).is_empty());
}
#[test]
fn a_suppression_with_neither_a_case_nor_a_reason_is_not_this_gates_business() {
let dir = tree(&[("src/client.rs", "#[allow(dead_code)]\nfn unused() {}\n")]);
assert!(run_in(&dir).is_empty());
}
#[test]
fn each_site_of_one_absent_case_is_reported() {
let dir = tree(&[
("src/a.rs", "// KI-gone\n"),
("src/b.rs", "x\n// KI-gone\n"),
]);
let out = run_in(&dir);
assert_eq!(out.len(), 2, "{out:?}");
assert!(out[0].contains("src/a.rs:1"));
assert!(out[1].contains("src/b.rs:2"));
}
#[test]
fn an_excluded_path_leaves_the_subject_set() {
let dir = tree(&[("_docs/specs/SPEC-spec-to-code.md", "cite KI-vendor-500\n")]);
assert_eq!(run_in(&dir).len(), 1, "the unfiltered scan reads it");
assert!(run(&excluding(&dir, "_docs/**"), &[]).unwrap().is_empty());
}
#[test]
fn the_records_resolve_a_case_even_where_they_are_excluded() {
let dir = tree(&[("src/client.rs", "// KI-vendor-replays\n"), RECORD]);
let ctx = excluding(&dir, "_docs/reference/known-issues/**");
assert!(run(&ctx, &[]).unwrap().is_empty());
}
#[test]
fn a_token_reads_only_on_its_own_word_boundaries() {
assert_eq!(
cited_cases("names KI-vendor-500 here").collect::<Vec<_>>(),
vec!["KI-vendor-500".to_string()]
);
assert!(cited_cases("WIKI-vendor").next().is_none());
assert_eq!(
cited_cases("KI-vendorXYZ").collect::<Vec<_>>(),
Vec::<String>::new()
);
assert!(cited_cases("KI- alone").next().is_none());
}
#[test]
fn a_binary_file_is_skipped() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("blob.bin"), b"KI-gone\0\0\0").unwrap();
assert!(run_in(&dir).is_empty());
}
#[test]
fn a_citation_survives_a_byte_this_process_cannot_decode() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("notes.txt"), b"\xe9\n// KI-gone\n").unwrap();
let out = run_in(&dir);
assert_eq!(out.len(), 1, "{out:?}");
assert!(out[0].contains("notes.txt:2"));
}
}