#![allow(
clippy::enum_glob_use,
clippy::too_many_lines,
clippy::wildcard_imports
)]
#![allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
use super::{Abc, Stats, count_boolean_slot, count_field_operands, last_operand, wrapped_operand};
use crate::lang_helpers::elixir::{elixir_applying_operator, elixir_call_keyword};
use crate::metrics::elixir_decisions::{
elixir_arrow_is_with_clause, elixir_clause_is_decision, elixir_sole_unguarded_pattern,
elixir_when_alternative, elixir_when_is_guard,
};
use crate::*;
const BINARY_OPERATOR: &str = "binary_operator";
fn elixir_wrapper_operand<'a>(node: &Node<'a>) -> Option<(Node<'a>, bool)> {
use Elixir as E;
match node.kind_id().into() {
E::Block => last_operand(node).map(|o| (o, false)),
E::UnaryOperator => node
.child_by_field_name("operator")
.filter(|op| matches!(op.kind_id().into(), E::BANG | E::Not))
.and_then(|_| node.child_by_field_name("operand"))
.map(|o| (o, true)),
_ if elixir_binary_operator_is(node, |op| op == E::EQ) => {
node.child_by_field_name("right").map(|o| (o, false))
}
_ => None,
}
}
fn elixir_binary_operator_is(node: &Node, accept: impl Fn(Elixir) -> bool) -> bool {
node.kind() == BINARY_OPERATOR
&& node
.child_by_field_name("operator")
.is_some_and(|op| accept(op.kind_id().into()))
}
fn elixir_condition_scores_itself(expr: &Node) -> bool {
use Elixir as E;
elixir_binary_operator_is(expr, |op| {
matches!(
op,
E::EQEQ
| E::EQEQEQ
| E::BANGEQ
| E::BANGEQEQ
| E::LTEQ
| E::GTEQ
| E::LT
| E::GT
| E::In
| E::Notin
| E::AMPAMP
| E::PIPEPIPE
| E::And
| E::Or
)
})
}
fn elixir_count_condition(condition: &Node, conditions: &mut f64) {
count_boolean_slot(
condition,
elixir_wrapper_operand,
elixir_condition_scores_itself,
conditions,
);
}
fn elixir_count_guard(when_operator: &Node, conditions: &mut f64) {
if let Some(alternative) = elixir_when_alternative(when_operator) {
elixir_count_condition(&alternative, conditions);
}
}
fn elixir_count_clause(clause: &Node, conditions: &mut f64) {
match elixir_sole_unguarded_pattern(clause) {
Some(pattern) => elixir_count_condition(&pattern, conditions),
None => *conditions += 1.,
}
}
fn elixir_has_else(call: &Node, code: &[u8]) -> bool {
use Elixir as E;
call.children().any(|child| match child.kind_id().into() {
E::DoBlock => child.children().any(|c| c.kind_id() == E::ElseBlock as u16),
_ if child.kind() == "arguments" => child
.children()
.filter(|c| c.kind_id() == E::Keywords as u16)
.flat_map(|keywords| keywords.children())
.filter_map(|pair| pair.child_by_field_name("key"))
.any(|key| key.utf8_text(code).is_some_and(|k| k.trim_end() == "else:")),
_ => false,
})
}
fn elixir_count_call(node: &Node, code: &[u8], stats: &mut Stats) {
let keyword = elixir_call_keyword(node, code);
let is_definition_or_directive = matches!(
keyword,
Some(
"def"
| "defp"
| "defmacro"
| "defmacrop"
| "defmodule"
| "defstruct"
| "defprotocol"
| "defimpl"
| "alias"
| "import"
| "require"
| "use"
)
);
if !is_definition_or_directive {
stats.branches += 1.;
}
if let Some("if" | "unless") = keyword {
if let Some(predicate) = node
.children()
.find(|c| c.kind() == "arguments")
.and_then(|args| wrapped_operand(&args))
{
elixir_count_condition(&predicate, &mut stats.conditions);
}
if elixir_has_else(node, code) {
stats.conditions += 1.;
}
}
}
impl Abc for ElixirCode {
fn compute<'a>(
node: &Node<'a>,
code: &'a [u8],
ancestors: Ancestors<'a, '_>,
stats: &mut Stats,
) {
use Elixir as E;
match node.kind_id().into() {
E::BinaryOperator | E::BinaryOperator2 | E::BinaryOperator3
if node
.child_by_field_name("operator")
.is_some_and(|c| c.kind_id() == E::EQ as u16) =>
{
stats.assignments += 1.;
}
E::PIPEGT => {
stats.branches += 1.;
}
E::Call => elixir_count_call(node, code, stats),
E::When if elixir_when_is_guard(node, code, ancestors) => {
if let Some(operator) = ancestors.parent(node) {
elixir_count_guard(&operator, &mut stats.conditions);
}
}
E::EQEQ
| E::EQEQEQ
| E::BANGEQ
| E::BANGEQEQ
| E::LTEQ
| E::GTEQ
| E::LT
| E::GT
| E::In
| E::Notin
if elixir_applying_operator(node, ancestors).is_some() =>
{
stats.conditions += 1.;
}
E::AMPAMP | E::PIPEPIPE | E::And | E::Or => {
if let Some(chain) = elixir_applying_operator(node, ancestors) {
count_field_operands(&chain, elixir_count_condition, &mut stats.conditions);
}
}
E::StabClause if elixir_clause_is_decision(node, code, ancestors) => {
elixir_count_clause(node, &mut stats.conditions);
}
E::LTDASH if elixir_arrow_is_with_clause(node, code, ancestors) => {
stats.conditions += 1.;
}
_ => {}
}
}
}