1mod cache;
5mod code_spans;
6mod compile;
7mod jslex;
8mod pattern;
9mod prefilter;
10mod prove;
11pub mod range;
12mod scan;
13pub mod secrets;
14pub mod terms;
15
16use crate::check::baseline;
17use crate::check::finding::Finding;
18use crate::config::{Config, GuardDef};
19use anyhow::{Result, anyhow, bail};
20pub use range::GuardRange;
21
22fn select<'a>(cfg: &'a Config, names: &[String]) -> Result<Vec<&'a GuardDef>> {
23 if names.is_empty() {
24 return Ok(cfg.raw.guards.iter().collect());
25 }
26 names
27 .iter()
28 .map(|n| {
29 cfg.raw.guards.iter().find(|g| &g.name == n).ok_or_else(|| {
30 let mut have: Vec<&str> = cfg.raw.guards.iter().map(|g| g.name.as_str()).collect();
32 have.push("secrets (built-in)");
33 anyhow!("no guard named '{n}' (have: {}); next: spec guard secrets", have.join(", "))
34 })
35 })
36 .collect()
37}
38
39fn scan_one(cfg: &Config, g: &GuardDef, range: Option<&GuardRange>) -> Result<scan::Scanned> {
40 let c = compile::compile(cfg, g)?;
41 prove::prove(g, &c)?;
42 let r = range.cloned().unwrap_or_else(|| GuardRange::parse(&g.range));
43 scan::run(cfg, g, &c, &range::resolve(&cfg.root, &r, &c.bases)?)
44}
45
46pub fn run(cfg: &Config, names: &[String], range: Option<&GuardRange>) -> Result<Vec<Finding>> {
49 let mut out = vec![];
50 for g in select(cfg, names)? {
51 let found = scan_one(cfg, g, range)?.findings;
52 match &g.baseline {
53 Some(rel) => match baseline::load_file(&cfg.root, rel)? {
54 Some(b) => out.extend(baseline::hold_with(&b, found, &[]).findings),
55 None => out.extend(found),
56 },
57 None => out.extend(found),
58 }
59 }
60 Ok(out)
61}
62
63pub fn write_baselines(cfg: &Config, names: &[String], write: bool) -> Result<Vec<(String, String, usize)>> {
66 let mut written = vec![];
67 for g in select(cfg, names)? {
68 let Some(rel) = &g.baseline else {
69 if !names.is_empty() {
70 bail!("guard {} has no baseline file; set baseline = \"…\" on it", g.name);
71 }
72 continue;
73 };
74 let scanned = scan_one(cfg, g, Some(&GuardRange::All))?;
75 let b = baseline::measure_with(&cfg.root, &scanned.findings, &[], |p| scanned.files.iter().any(|f| f == p))?;
76 if write {
77 baseline::write_file(&cfg.root, rel, &b)?;
78 }
79 written.push((g.name.clone(), rel.clone(), b.counts.values().sum()));
80 }
81 Ok(written)
82}
83
84mod blobs;
85#[cfg(test)]
86mod tests;
87pub use blobs::{builtin_secrets, reads_content, scan_blobs};