use alloc::vec::Vec as StdVec;
use vize_l0::Span;
use vize_l0::id::NodeId;
use vize_l0::pass::{Fusability, PassDesc, PassKind, Preserved};
use crate::op::Region;
use crate::walk::{PageWalk, assert_accounting};
mod expr;
mod print;
mod regions;
pub use print::print_facts;
pub const NAME: &str = "template-complexity";
pub const DESC: PassDesc = PassDesc::new(
NAME,
PassKind::Optional,
Fusability::Fusable,
Preserved::ALL,
);
pub const CYCLOMATIC_WARN_ABOVE: u32 = 11;
pub const COGNITIVE_WARN_ABOVE: u32 = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DecisionKind {
If,
ElseIf,
Else,
For,
ScopedSlot,
Logical,
Conditional,
Unknown,
}
impl DecisionKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::If => "v-if",
Self::ElseIf => "v-else-if",
Self::Else => "v-else",
Self::For => "v-for",
Self::ScopedSlot => "scoped-slot",
Self::Logical => "logical",
Self::Conditional => "conditional",
Self::Unknown => "unknown",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Contribution {
pub span: Span,
pub kind: DecisionKind,
pub op: Option<NodeId>,
pub nesting: u32,
pub cyclomatic: u32,
pub cognitive: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ComplexityFacts {
pub cyclomatic: u32,
pub cognitive: u32,
pub unknown: u32,
pub max_nesting: u32,
pub contributions: StdVec<Contribution>,
}
impl Contribution {
#[must_use]
pub const fn sort_key(&self) -> (u32, u32, DecisionKind) {
(self.span.start, self.span.end, self.kind)
}
}
const _: () = {
const fn assert_owned<T: 'static>() {}
assert_owned::<ComplexityFacts>();
};
#[cfg(target_pointer_width = "64")]
const _: () = assert!(core::mem::size_of::<Contribution>() == 28);
impl ComplexityFacts {
#[must_use]
pub const fn empty() -> Self {
Self {
cyclomatic: 1,
cognitive: 0,
unknown: 0,
max_nesting: 0,
contributions: StdVec::new(),
}
}
#[must_use]
pub fn shifted(mut self, offset: u32) -> Self {
for row in &mut self.contributions {
row.span = Span::new(
row.span.start.saturating_add(offset),
row.span.end.saturating_add(offset),
);
}
self
}
#[must_use]
pub const fn exceeds_default_thresholds(&self) -> bool {
self.cyclomatic > CYCLOMATIC_WARN_ABOVE || self.cognitive > COGNITIVE_WARN_ABOVE
}
pub(super) fn push(&mut self, contribution: Contribution) {
self.cyclomatic = self.cyclomatic.saturating_add(contribution.cyclomatic);
self.cognitive = self.cognitive.saturating_add(contribution.cognitive);
if contribution.kind == DecisionKind::Unknown {
self.unknown = self.unknown.saturating_add(1);
}
self.contributions.push(contribution);
}
pub(super) fn settle(&mut self, index: usize, cyclomatic: u32, cognitive: u32) {
if let Some(row) = self.contributions.get_mut(index) {
row.cyclomatic = cyclomatic;
row.cognitive = cognitive;
self.cyclomatic = self.cyclomatic.saturating_add(cyclomatic);
self.cognitive = self.cognitive.saturating_add(cognitive);
}
}
}
#[must_use]
pub fn run(root: &Region<'_>, node_count: u32) -> ComplexityFacts {
let mut facts = ComplexityFacts::empty();
let mut walk = PageWalk::new();
regions::visit_region(&mut walk, &root.ops, 0, &mut facts);
assert_accounting(&walk, node_count, NAME);
facts
.contributions
.sort_unstable_by_key(Contribution::sort_key);
facts
}