use crate::expr::ExprRef;
use oxc_ast::ast::{ConditionalExpression, Expression, LogicalExpression, LogicalOperator};
use oxc_ast_visit::{Visit, walk};
use vize_l0::Span;
use vize_l0::id::NodeId;
use super::{ComplexityFacts, Contribution, DecisionKind};
pub(super) fn score(
expr: ExprRef<'_>,
op: Option<NodeId>,
depth: u32,
facts: &mut ComplexityFacts,
) {
match expr {
ExprRef::Js(js) => {
let mut scorer = Scorer {
base: js.span.start,
op,
depth,
conditionals: 0,
facts,
};
scorer.visit_expression(js.ast);
}
ExprRef::Opaque(_) | ExprRef::Foreign(_) | ExprRef::Filter(_) => {
facts.push(Contribution {
span: expr.span(),
kind: DecisionKind::Unknown,
op,
nesting: depth,
cyclomatic: 0,
cognitive: 0,
});
}
}
}
struct Scorer<'f> {
base: u32,
op: Option<NodeId>,
depth: u32,
conditionals: u32,
facts: &'f mut ComplexityFacts,
}
struct Run {
operators: u32,
runs: u32,
last: Option<LogicalOperator>,
}
impl Scorer<'_> {
fn file_span(&self, span: oxc_span::Span) -> Span {
Span::new(
self.base.saturating_add(span.start),
self.base.saturating_add(span.end),
)
}
fn nesting(&self) -> u32 {
self.depth.saturating_add(self.conditionals)
}
fn flatten<'a>(&mut self, logical: &LogicalExpression<'a>, run: &mut Run) {
self.operand(&logical.left, run);
run.operators = run.operators.saturating_add(1);
let operator = Some(logical.operator);
if run.last != operator {
run.runs = run.runs.saturating_add(1);
run.last = operator;
}
self.operand(&logical.right, run);
}
fn operand<'a>(&mut self, operand: &Expression<'a>, run: &mut Run) {
match operand.without_parentheses() {
Expression::LogicalExpression(inner) => {
vize_l0::ensure_sufficient_stack(|| self.flatten(inner, run));
}
_ => self.visit_expression(operand),
}
}
}
impl<'a> Visit<'a> for Scorer<'_> {
fn visit_logical_expression(&mut self, it: &LogicalExpression<'a>) {
let index = self.facts.contributions.len();
self.facts.push(Contribution {
span: self.file_span(it.span),
kind: DecisionKind::Logical,
op: self.op,
nesting: self.nesting(),
cyclomatic: 0,
cognitive: 0,
});
let mut run = Run {
operators: 0,
runs: 0,
last: None,
};
self.flatten(it, &mut run);
self.facts.settle(index, run.operators, run.runs);
}
fn visit_conditional_expression(&mut self, it: &ConditionalExpression<'a>) {
let nesting = self.nesting();
self.facts.push(Contribution {
span: self.file_span(it.span),
kind: DecisionKind::Conditional,
op: self.op,
nesting,
cyclomatic: 1,
cognitive: 1 + nesting,
});
self.conditionals += 1;
vize_l0::ensure_sufficient_stack(|| walk::walk_conditional_expression(self, it));
self.conditionals -= 1;
}
}