use std::collections::BTreeMap;
use cozo::DataValue;
pub const MAX_DEPTH: u32 = 32;
fn clamp_depth(depth: u32) -> u32 {
depth.clamp(1, MAX_DEPTH)
}
fn seed_params(seed: &str) -> BTreeMap<String, DataValue> {
let mut p = BTreeMap::new();
p.insert("seed".into(), DataValue::from(seed));
p
}
pub fn blast_radius(seed: &str, depth: u32) -> (String, BTreeMap<String, DataValue>) {
let depth = clamp_depth(depth);
let script = format!(
"ancestors[id, dist] := *Calls{{src: id, dst: $seed}}, dist = 1\n\
ancestors[id, dist] := ancestors[m, d], *Calls{{src: id, dst: m}}, \
dist = d + 1, dist <= {depth}\n\
?[id, dist] := ancestors[id, dist] :order dist :limit 5000\n"
);
(script, seed_params(seed))
}
pub fn call_chain(seed: &str, depth: u32) -> (String, BTreeMap<String, DataValue>) {
let depth = clamp_depth(depth);
let script = format!(
"reach[id, dist] := *Calls{{src: $seed, dst: id}}, dist = 1\n\
reach[id, dist] := reach[m, d], *Calls{{src: m, dst: id}}, \
dist = d + 1, dist <= {depth}\n\
?[id, dist] := reach[id, dist] :order dist :limit 5000\n"
);
(script, seed_params(seed))
}
pub const DEAD_CODE: &str = "
?[id, qname, kind, file, line] :=
*Symbol{id, qname, kind, file, line},
(kind = \"function\" or kind = \"method\" or kind = \"constructor\"),
not *Calls{dst: id}
:limit 1000
";
pub fn dead_code_with_limit(limit: u32) -> (String, BTreeMap<String, DataValue>) {
let limit = limit.clamp(1, 200_000);
let script = format!(
"?[id, qname, kind, file, line] :=\n\
*Symbol{{id, qname, kind, file, line}},\n\
(kind = \"function\" or kind = \"method\" or kind = \"constructor\"),\n\
not *Calls{{dst: id}}\n\
:limit {limit}\n"
);
(script, BTreeMap::new())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn blast_uses_seed_param_not_interpolation() {
let (q, p) = blast_radius("a\"b\nseparator", 2);
assert!(q.contains("$seed"));
assert!(!q.contains("a\"b"));
assert!(q.contains("dist <= 2"));
assert_eq!(p.get("seed"), Some(&DataValue::from("a\"b\nseparator")));
}
#[test]
fn call_chain_includes_depth_and_seed_param() {
let (q, p) = call_chain("seed", 5);
assert!(q.contains("dist <= 5"));
assert!(q.contains("$seed"));
assert_eq!(p.get("seed"), Some(&DataValue::from("seed")));
}
#[test]
fn depth_is_clamped() {
let (q, _) = blast_radius("x", 9999);
assert!(q.contains(&format!("dist <= {MAX_DEPTH}")));
let (q, _) = call_chain("x", 0);
assert!(q.contains("dist <= 1"));
}
}