use alloc::vec::Vec;
use vize_l0::id::NodeId;
use vize_l0::{Span, String};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProvenanceRecord {
pub rule: String,
pub node: Option<NodeId>,
pub before: String,
pub after: String,
pub span: Span,
}
const _: () = {
const fn assert_owned<T: 'static>() {}
assert_owned::<ProvenanceRecord>();
};
pub trait ProvenanceSink {
fn kept(&mut self) -> Option<&mut Vec<ProvenanceRecord>>;
#[inline]
fn record(&mut self, build: impl FnOnce() -> ProvenanceRecord) {
if let Some(records) = self.kept() {
records.push(build());
}
}
}
impl ProvenanceSink for Vec<ProvenanceRecord> {
#[inline]
fn kept(&mut self) -> Option<&mut Vec<ProvenanceRecord>> {
Some(self)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct NoProvenance;
impl ProvenanceSink for NoProvenance {
#[inline]
fn kept(&mut self) -> Option<&mut Vec<ProvenanceRecord>> {
None
}
}
#[cfg(target_pointer_width = "64")]
const _: () = assert!(core::mem::size_of::<ProvenanceRecord>() == 88);
#[cfg(test)]
mod tests {
use super::{NoProvenance, ProvenanceRecord, ProvenanceSink};
use alloc::vec::Vec;
use core::cell::Cell;
use vize_l0::id::NodeId;
use vize_l0::{Span, String};
fn decision(built: &Cell<u32>) -> impl FnOnce() -> ProvenanceRecord + '_ {
|| {
built.set(built.get() + 1);
ProvenanceRecord {
rule: String::from("lower.text"),
node: NodeId::from_index(0),
before: String::from("t"),
after: String::from("ui.text"),
span: Span::new(0, 1),
}
}
}
#[test]
fn only_a_kept_sink_builds_records() {
let built = Cell::new(0);
let mut none = NoProvenance;
none.record(decision(&built));
assert_eq!(built.get(), 0);
assert_eq!(none.kept(), None);
let mut kept = Vec::new();
kept.record(decision(&built));
assert_eq!(built.get(), 1);
assert_eq!(kept.len(), 1);
assert_eq!(kept[0].after.as_str(), "ui.text");
}
#[test]
fn a_producing_decision_links_its_op() {
let record = ProvenanceRecord {
rule: String::from("lower.for"),
node: NodeId::from_index(3),
before: String::from("item of items"),
after: String::from("ui.for source=js value=js"),
span: Span::new(12, 25),
};
assert_eq!(record.node, NodeId::from_index(3));
assert_eq!(record.before.as_str(), "item of items");
assert!(!record.after.is_empty());
}
#[test]
fn a_failed_decision_is_still_a_record() {
let record = ProvenanceRecord {
rule: String::from("defer.v-bind"),
node: None,
before: String::from(":id=\"x\""),
after: String::default(),
span: Span::new(5, 12),
};
assert_eq!(record.node, None);
assert!(record.after.is_empty());
assert_eq!(record.rule.as_str(), "defer.v-bind");
}
}