#![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_each_operand, count_field_operands, last_operand,
wrapped_operand,
};
use crate::lang_helpers::elixir::{
elixir_applying_operator, elixir_applying_remote_call, elixir_call_keyword,
elixir_kernel_short_circuit_name,
};
use crate::metrics::elixir_decisions::{
Section, elixir_arrow_is_with_clause, elixir_call_has_section, 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, code: &[u8]) -> bool {
use Elixir as E;
elixir_kernel_short_circuit_name(expr, code).is_some()
|| 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, code: &[u8], conditions: &mut f64) {
count_boolean_slot(
condition,
elixir_wrapper_operand,
|expr| elixir_condition_scores_itself(expr, code),
conditions,
);
}
fn elixir_count_guard(when_operator: &Node, code: &[u8], conditions: &mut f64) {
if let Some(alternative) = elixir_when_alternative(when_operator) {
elixir_count_condition(&alternative, code, conditions);
}
}
fn elixir_count_clause(clause: &Node, code: &[u8], conditions: &mut f64) {
match elixir_sole_unguarded_pattern(clause) {
Some(pattern) => elixir_count_condition(&pattern, code, conditions),
None => *conditions += 1.,
}
}
fn elixir_has_else(call: &Node, code: &[u8]) -> bool {
elixir_call_has_section(call, code, Section::Else)
}
fn elixir_count_remote_chain<'a>(
op: &Node<'a>,
code: &'a [u8],
ancestors: Ancestors<'a, '_>,
conditions: &mut f64,
) {
let Some(remote) = elixir_applying_remote_call(op, code, ancestors) else {
return;
};
let score = |operand: &Node, conditions: &mut f64| {
elixir_count_condition(operand, code, conditions);
};
if let Some(piped) = remote
.ancestors
.parent(&remote.call)
.filter(|pipe| elixir_binary_operator_is(pipe, |op| op == Elixir::PIPEGT))
.and_then(|pipe| pipe.child_by_field_name("left"))
{
score(&piped, conditions);
}
count_each_operand(&remote.arguments, score, conditions);
}
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 keyword == Some("receive")
&& let Some(timeout) =
crate::metrics::elixir_decisions::elixir_receive_timeout_clause(node, code)
{
elixir_count_clause(&timeout, code, &mut stats.conditions);
}
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, code, &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, code, &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,
|operand, conditions| elixir_count_condition(operand, code, conditions),
&mut stats.conditions,
);
} else {
elixir_count_remote_chain(node, code, ancestors, &mut stats.conditions);
}
}
E::Identifier => {
elixir_count_remote_chain(node, code, ancestors, &mut stats.conditions);
}
E::StabClause if elixir_clause_is_decision(node, code, ancestors) => {
elixir_count_clause(node, code, &mut stats.conditions);
}
E::LTDASH if elixir_arrow_is_with_clause(node, code, ancestors) => {
stats.conditions += 1.;
}
_ => {}
}
}
}