#![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,
count_negated_operand, peel, wrapped_operand,
};
use crate::*;
fn groovy_wrapper_operand<'a>(node: &Node<'a>) -> Option<(Node<'a>, bool)> {
use Groovy::*;
match node.kind_id().into() {
ParenthesizedExpression => wrapped_operand(node).map(|o| (o, false)),
CastExpression | ParenthesizedTypeCast => {
node.child_by_field_name("value").map(|o| (o, false))
}
UnaryExpression => {
let operand = node.child_by_field_name("operand")?;
match node.child_by_field_name("operator")?.kind_id().into() {
BANG => Some((operand, true)),
_ => None,
}
}
_ => None,
}
}
fn groovy_condition_scores_itself(expr: &Node) -> bool {
use Groovy::*;
match expr.kind_id().into() {
IdentityExpression | RegexFindExpression | RegexMatchExpression | SpaceshipExpression
| MembershipExpression | InstanceofExpression | TernaryExpression | ElvisExpression => true,
BinaryExpression => expr.child_by_field_name("operator").is_some_and(|op| {
matches!(
op.kind_id().into(),
EQEQ | BANGEQ | LT | GT | LTEQ | GTEQ | AMPAMP | PIPEPIPE
)
}),
AssignmentExpression => expr.child_by_field_name("right").is_some_and(|right| {
let (value, proves_boolean) = peel(&right, groovy_wrapper_operand);
proves_boolean || groovy_condition_scores_itself(&value)
}),
_ => false,
}
}
fn groovy_count_condition(condition: &Node, conditions: &mut f64) {
count_boolean_slot(
condition,
groovy_wrapper_operand,
groovy_condition_scores_itself,
conditions,
);
}
fn groovy_count_negated(operand: &Node, conditions: &mut f64) {
count_negated_operand(
operand,
groovy_wrapper_operand,
groovy_condition_scores_itself,
conditions,
);
}
fn groovy_count_slot(slot: Option<Node>, conditions: &mut f64) {
if let Some(condition) = slot {
groovy_count_condition(&condition, conditions);
}
}
fn groovy_count_negated_slot(slot: Option<Node>, conditions: &mut f64) {
if let Some(operand) = slot {
groovy_count_negated(&operand, conditions);
}
}
fn groovy_eq_initializes_final_binding<'a>(
eq_node: &Node<'a>,
ancestors: Ancestors<'a, '_>,
) -> bool {
use Groovy::*;
eq_node.parent_grandparent_match(
ancestors,
|parent| parent.kind_id() == VariableDeclarator,
|declaration| {
matches!(
declaration.kind_id().into(),
FieldDeclaration | LocalVariableDeclaration
) && declaration
.children()
.take_while(|child| child.kind_id() != VariableDeclarator)
.any(|child| child.kind_id() == Final)
},
)
}
fn groovy_count_token_assignment<'a>(
node: &Node<'a>,
ancestors: Ancestors<'a, '_>,
stats: &mut Stats,
) -> bool {
use Groovy::*;
match node.kind_id().into() {
STAREQ | SLASHEQ | PERCENTEQ | DASHEQ | PLUSEQ | LTLTEQ | GTGTEQ | AMPEQ | PIPEEQ
| CARETEQ | GTGTGTEQ | PLUSPLUS | DASHDASH => {
stats.assignments += 1.;
}
EQ => {
if !groovy_eq_initializes_final_binding(node, ancestors) {
stats.assignments += 1.;
}
}
_ => return false,
}
true
}
fn groovy_count_token_branch(node: &Node, stats: &mut Stats) -> bool {
use Groovy::*;
let is_branch = match node.kind_id().into() {
MethodInvocation | CommandChain | New => true,
EnumConstant => node.is_child(ArgumentList as u16),
_ => false,
};
if is_branch {
stats.branches += 1.;
}
is_branch
}
fn groovy_count_token_condition<'a>(
node: &Node<'a>,
ancestors: Ancestors<'a, '_>,
stats: &mut Stats,
) -> bool {
use Groovy::*;
match node.kind_id().into() {
GTEQ | LTEQ | LTEQGT | EQEQ | BANGEQ | EQEQEQ | BANGEQEQ | EQTILDE | EQEQTILDE | Else
| Case | Try | Catch | QMARKCOLON | MembershipExpression | InstanceofExpression => {
stats.conditions += 1.;
}
QMARK => {
if ancestors
.parent(node)
.is_some_and(|parent| matches!(parent.kind_id().into(), TernaryExpression))
{
stats.conditions += 1.;
}
}
GT | LT => {
if ancestors
.parent(node)
.is_some_and(|parent| matches!(parent.kind_id().into(), BinaryExpression))
{
stats.conditions += 1.;
}
}
_ => return false,
}
true
}
fn groovy_walk_for_conditions<'a>(
node: &Node<'a>,
ancestors: Ancestors<'a, '_>,
stats: &mut Stats,
) {
use Groovy::*;
let conds = &mut stats.conditions;
match node.kind_id().into() {
AMPAMP | PIPEPIPE => {
if let Some(chain) = ancestors
.parent(node)
.filter(|p| p.kind_id() == BinaryExpression)
{
count_field_operands(&chain, groovy_count_condition, conds);
}
}
ArgumentList => count_each_operand(node, groovy_count_negated, conds),
VariableDeclarator => groovy_count_negated_slot(node.child_by_field_name("value"), conds),
AssignmentExpression => {
groovy_count_negated_slot(node.child_by_field_name("right"), conds);
}
IfStatement | WhileStatement | DoWhileStatement => {
groovy_count_slot(node.child_by_field_name("condition"), conds);
}
ReturnStatement => groovy_count_negated_slot(wrapped_operand(node), conds),
TernaryExpression => groovy_walk_ternary(node, stats),
ForStatement => groovy_walk_for_statement(node, stats),
_ => {}
}
}
fn groovy_walk_ternary(node: &Node, stats: &mut Stats) {
let conds = &mut stats.conditions;
groovy_count_slot(node.child_by_field_name("condition"), conds);
for field in ["consequence", "alternative"] {
groovy_count_negated_slot(node.child_by_field_name(field), conds);
}
}
fn groovy_walk_for_statement(node: &Node, stats: &mut Stats) {
groovy_count_slot(node.child_by_field_name("condition"), &mut stats.conditions);
}
impl Abc for GroovyCode {
fn compute<'a>(
node: &Node<'a>,
_code: &'a [u8],
ancestors: Ancestors<'a, '_>,
stats: &mut Stats,
) {
if groovy_count_token_assignment(node, ancestors, stats) {
return;
}
if groovy_count_token_branch(node, stats) {
return;
}
if groovy_count_token_condition(node, ancestors, stats) {
return;
}
groovy_walk_for_conditions(node, ancestors, stats);
}
}