use crate::finding::Finding;
use crate::hooks::{ban_terms, large_files, merge_conflict, secrets};
pub fn scan(file: &str, bytes: &[u8]) -> Vec<Finding> {
let mut out = Vec::new();
if let Some(f) = large_files::scan(file, bytes.len() as u64) {
out.push(f);
}
if !secrets::is_scannable(bytes) {
return sorted(out);
}
let text = String::from_utf8_lossy(bytes);
out.extend(ban_terms::scan(file, &text, ban_terms::NAME));
out.extend(merge_conflict::scan(file, &text, merge_conflict::NAME));
out.extend(secrets::findings(file, &text));
sorted(out)
}
fn sorted(mut findings: Vec<Finding>) -> Vec<Finding> {
findings.sort_by(|a, b| {
a.file
.cmp(&b.file)
.then(a.line.cmp(&b.line))
.then(a.column.cmp(&b.column))
.then(a.check.cmp(b.check))
});
findings
}
#[cfg(test)]
mod tests {
use super::*;
use crate::check::Severity;
#[test]
fn a_clean_file_has_nothing_to_say() {
assert!(scan("app.js", b"const x = 1;\n").is_empty());
}
#[test]
fn a_banned_term_is_placed() {
let f = scan("app.js", b"const x = 1;\n\n debugger;\n");
assert_eq!(f.len(), 1);
assert_eq!(f[0].check, "ban-terms");
assert_eq!(f[0].line, Some(3));
assert_eq!(f[0].column, Some(3));
}
#[test]
fn scope_is_respected_outside_the_index_too() {
assert!(scan("notes.txt", b"debugger;\n").is_empty());
}
#[test]
fn a_binary_file_is_sized_but_not_read() {
let mut bytes = vec![0u8; 200];
bytes.extend_from_slice(b"debugger;");
let f = scan("blob.js", &bytes);
assert!(f.iter().all(|f| f.check != "ban-terms"), "{f:?}");
}
#[test]
fn findings_are_ordered_for_reading() {
let whole = Finding::new("large-files", "a.js", Severity::Warn, "x");
let late = Finding::new("ban-terms", "a.js", Severity::Block, "y").at_line(9);
let early = Finding::new("secrets", "a.js", Severity::Block, "z").at_line(2);
let out = sorted(vec![late, whole, early]);
assert_eq!(
out.iter().map(|f| f.line).collect::<Vec<_>>(),
vec![None, Some(2), Some(9)]
);
}
}