use crate::op::{BindingOp, DynamicName, IfOp, Op};
use crate::walk::PageWalk;
use vize_l0::ensure_sufficient_stack;
use vize_l0::id::NodeId;
use super::expr::score;
use super::{ComplexityFacts, Contribution, DecisionKind};
pub(super) fn visit_region(
walk: &mut PageWalk,
ops: &[Op<'_>],
depth: u32,
facts: &mut ComplexityFacts,
) {
ensure_sufficient_stack(|| visit_region_guarded(walk, ops, depth, facts));
}
fn visit_region_guarded(
walk: &mut PageWalk,
ops: &[Op<'_>],
depth: u32,
facts: &mut ComplexityFacts,
) {
for op in ops {
let id = walk.mint();
match op {
Op::Element(element) => {
let inner = owner_bindings(walk, &element.bindings, depth, facts);
visit_nested(walk, &element.children.ops, inner, facts);
}
Op::Component(component) => {
let inner = owner_bindings(walk, &component.bindings, depth, facts);
visit_nested(walk, &component.children.ops, inner, facts);
}
Op::Text(_) | Op::Comment(_) => {}
Op::Interpolation(interpolation) => {
score(interpolation.expression, id, depth, facts);
}
Op::If(if_op) => visit_if(walk, if_op, id, depth, facts),
Op::For(for_op) => {
facts.push(Contribution {
span: for_op.binding.source.span(),
kind: DecisionKind::For,
op: id,
nesting: depth,
cyclomatic: 1,
cognitive: 1 + depth,
});
score(for_op.binding.source, id, depth, facts);
visit_nested(walk, &for_op.region.ops, depth + 1, facts);
}
Op::OriginalFor(for_op) => {
facts.push(Contribution {
span: for_op.span,
kind: DecisionKind::For,
op: id,
nesting: depth,
cyclomatic: 1,
cognitive: 1 + depth,
});
visit_nested(walk, &for_op.region.ops, depth + 1, facts);
}
Op::Slot(slot) => {
if let DynamicName::Dynamic(name) = slot.name {
score(name, id, depth, facts);
}
let _ = owner_bindings(walk, &slot.bindings, depth, facts);
visit_nested(walk, &slot.fallback.ops, depth, facts);
}
}
}
}
fn visit_nested(walk: &mut PageWalk, ops: &[Op<'_>], depth: u32, facts: &mut ComplexityFacts) {
if ops.is_empty() {
return;
}
facts.max_nesting = facts.max_nesting.max(depth);
visit_region(walk, ops, depth, facts);
}
fn visit_if(
walk: &mut PageWalk,
if_op: &IfOp<'_>,
id: Option<NodeId>,
depth: u32,
facts: &mut ComplexityFacts,
) {
for (index, branch) in if_op.branches.iter().enumerate() {
match branch.condition {
Some(condition) => {
let (kind, cognitive) = if index == 0 {
(DecisionKind::If, 1 + depth)
} else {
(DecisionKind::ElseIf, 1)
};
facts.push(Contribution {
span: condition.span(),
kind,
op: id,
nesting: depth,
cyclomatic: 1,
cognitive,
});
score(condition, id, depth, facts);
}
None => facts.push(Contribution {
span: branch.span,
kind: DecisionKind::Else,
op: id,
nesting: depth,
cyclomatic: 0,
cognitive: 1,
}),
}
visit_nested(walk, &branch.region.ops, depth + 1, facts);
}
}
fn owner_bindings(
walk: &mut PageWalk,
bindings: &[BindingOp<'_>],
depth: u32,
facts: &mut ComplexityFacts,
) -> u32 {
let mut scoped = false;
for binding in bindings {
let id = walk.mint();
match binding {
BindingOp::Bind(bind) => {
dynamic_name(bind.name, id, depth, facts);
if let Some(value) = bind.value {
score(value, id, depth, facts);
}
}
BindingOp::On(on) => {
dynamic_name(on.name, id, depth, facts);
if let Some(handler) = on.expression() {
score(handler, id, depth, facts);
}
}
BindingOp::Model(model) => {
dynamic_name(model.argument, id, depth, facts);
score(model.contract.read, id, depth, facts);
}
BindingOp::SlotContent(content) => {
dynamic_name(content.name, id, depth, facts);
if content.params.is_some() {
scoped |= scoped_slot(content.span, id, depth, facts);
}
}
BindingOp::VueSlotScope(scope) => {
if scope.params.is_some() {
scoped |= scoped_slot(scope.span, id, depth, facts);
}
}
BindingOp::VueDirective(directive) => {
dynamic_name(directive.argument, id, depth, facts);
if let Some(value) = directive.value {
score(value, id, depth, facts);
}
}
BindingOp::SlotsObject(object) => score(object.value, id, depth, facts),
BindingOp::VueSync(sync) => score(sync.value, id, depth, facts),
BindingOp::VueMemo(memo) => score(memo.value, id, depth, facts),
BindingOp::VueShow(show) => score(show.value, id, depth, facts),
BindingOp::VueHtml(html) => {
if let Some(value) = html.value {
score(value, id, depth, facts);
}
}
BindingOp::VueText(text) => {
if let Some(value) = text.value {
score(value, id, depth, facts);
}
}
BindingOp::VueCssBind(_) | BindingOp::VueOnce(_) | BindingOp::VueCloak(_) => {}
}
}
if scoped { depth + 1 } else { depth }
}
fn dynamic_name(
name: Option<DynamicName<'_>>,
id: Option<NodeId>,
depth: u32,
facts: &mut ComplexityFacts,
) {
if let Some(DynamicName::Dynamic(expr)) = name {
score(expr, id, depth, facts);
}
}
fn scoped_slot(
span: vize_l0::Span,
id: Option<NodeId>,
depth: u32,
facts: &mut ComplexityFacts,
) -> bool {
facts.push(Contribution {
span,
kind: DecisionKind::ScopedSlot,
op: id,
nesting: depth,
cyclomatic: 0,
cognitive: 0,
});
true
}