#![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};
use crate::macros::{
javascript_bool_terminal_kinds, mozjs_bool_terminal_kinds, tsx_bool_terminal_kinds,
typescript_bool_terminal_kinds,
};
use crate::*;
macro_rules! impl_js_family_unary_walker {
(
$Lang:ident,
$inspect:ident,
$count:ident,
$count_condition:ident,
$walk_ternary:ident,
$walk_for:ident,
$terminals:path
) => {
fn $inspect(container_node: &Node, parent: &Node, conditions: &mut f64) {
use $Lang::*;
let mut node = *container_node;
let mut node_kind = node.kind_id().into();
let parent_kind = parent.kind_id().into();
let mut has_boolean_content = matches!(
parent_kind,
BinaryExpression | IfStatement | WhileStatement | DoStatement | ForStatement
) || (matches!(parent_kind, TernaryExpression)
&& parent
.child_by_field_name("condition")
.is_some_and(|condition| condition.id() == node.id()));
loop {
let is_parens = matches!(node_kind, ParenthesizedExpression);
let is_not = matches!(node_kind, UnaryExpression)
&& node.child(0).is_some_and(|c| c.kind_id() == BANG as u16);
if !is_parens && !is_not {
break;
}
if !has_boolean_content && is_not {
has_boolean_content = true;
}
let Some(child) = node.child(1) else { break };
node = child;
node_kind = node.kind_id().into();
if matches!(node_kind, $terminals!()) {
if has_boolean_content {
*conditions += 1.;
}
break;
}
}
}
fn $walk_ternary(node: &Node, conditions: &mut f64) {
if let Some(condition) = node.child_by_field_name("condition") {
$count_condition(&condition, node, conditions);
}
for field in ["consequence", "alternative"] {
if let Some(branch) = node.child_by_field_name(field) {
$inspect(&branch, node, conditions);
}
}
}
fn $count_condition(condition: &Node, parent: &Node, conditions: &mut f64) {
if matches!(condition.kind_id().into(), $terminals!()) {
*conditions += 1.;
} else {
$inspect(condition, parent, conditions);
}
}
fn $walk_for(node: &Node, conditions: &mut f64) {
if let Some(condition) = node.child_by_field_name("condition") {
$count_condition(&condition, node, conditions);
}
}
fn $count(list_node: &Node, conditions: &mut f64) {
use $Lang::*;
let list_kind = list_node.kind_id().into();
let mut cursor = list_node.cursor();
if cursor.goto_first_child() {
loop {
let node = cursor.node();
let node_kind = node.kind_id().into();
if matches!(node_kind, $terminals!()) && matches!(list_kind, BinaryExpression) {
*conditions += 1.;
} else if node.is_named() {
$inspect(&node, list_node, conditions);
}
if !cursor.goto_next_sibling() {
break;
}
}
}
}
};
}
impl_js_family_unary_walker!(
Typescript,
typescript_inspect_container,
typescript_count_unary_conditions,
typescript_count_condition,
typescript_walk_ternary,
typescript_walk_for_statement,
typescript_bool_terminal_kinds
);
impl_js_family_unary_walker!(
Tsx,
tsx_inspect_container,
tsx_count_unary_conditions,
tsx_count_condition,
tsx_walk_ternary,
tsx_walk_for_statement,
tsx_bool_terminal_kinds
);
impl_js_family_unary_walker!(
Javascript,
javascript_inspect_container,
javascript_count_unary_conditions,
javascript_count_condition,
javascript_walk_ternary,
javascript_walk_for_statement,
javascript_bool_terminal_kinds
);
impl_js_family_unary_walker!(
Mozjs,
mozjs_inspect_container,
mozjs_count_unary_conditions,
mozjs_count_condition,
mozjs_walk_ternary,
mozjs_walk_for_statement,
mozjs_bool_terminal_kinds
);
macro_rules! impl_js_family_const_binding {
($Lang:ident, $name:ident) => {
fn $name<'a>(eq_node: &Node<'a>, ancestors: Ancestors<'a, '_>) -> bool {
use $Lang::*;
let mut climb = ancestors.iter(eq_node).map(|(ancestor, _)| ancestor);
let mut owner = climb.next();
while let Some(node) = owner
&& matches!(
node.kind_id().into(),
ObjectPattern
| ArrayPattern
| PairPattern
| ObjectAssignmentPattern
| AssignmentPattern
| RestPattern
)
{
owner = climb.next();
}
if !owner.is_some_and(|node| node.kind_id() == VariableDeclarator) {
return false;
}
climb.next().is_some_and(|declaration| {
declaration.kind_id() == LexicalDeclaration
&& declaration
.child_by_field_name("kind")
.is_some_and(|keyword| keyword.kind_id() == Const)
})
}
};
}
impl_js_family_const_binding!(Typescript, typescript_eq_initializes_const_binding);
impl_js_family_const_binding!(Tsx, tsx_eq_initializes_const_binding);
impl_js_family_const_binding!(Javascript, javascript_eq_initializes_const_binding);
impl_js_family_const_binding!(Mozjs, mozjs_eq_initializes_const_binding);
macro_rules! ts_abc_compute {
(
$lang:ident,
$count_unary:path,
$inspect_container:path,
$walk_ternary:path,
$walk_for:path,
$const_binding:path
) => {
fn compute<'a>(
node: &Node<'a>,
_code: &'a [u8],
ancestors: Ancestors<'a, '_>,
stats: &mut Stats,
) {
use $lang::*;
match node.kind_id().into() {
PLUSEQ | DASHEQ | STAREQ | SLASHEQ | PERCENTEQ | STARSTAREQ | AMPEQ | PIPEEQ
| CARETEQ | LTLTEQ | GTGTEQ | GTGTGTEQ | AMPAMPEQ | PIPEPIPEEQ | QMARKQMARKEQ
| PLUSPLUS | DASHDASH => {
stats.assignments += 1.;
}
EQ if !$const_binding(node, ancestors) => {
stats.assignments += 1.;
}
CallExpression | NewExpression => {
stats.branches += 1.;
}
EQEQ | EQEQEQ | BANGEQ | BANGEQEQ | LTEQ | GTEQ | QMARKQMARK | Instanceof
| Else | Case | Try | Catch => {
stats.conditions += 1.;
}
QMARK if ancestors.parent_has_kind(node, TernaryExpression as u16) => {
stats.conditions += 1.;
}
GT | LT
if ancestors.parent(node).is_some_and(|p| {
!matches!(p.kind_id().into(), TypeArguments | TypeParameters)
}) =>
{
stats.conditions += 1.;
}
AMPAMP | PIPEPIPE => {
if let Some(parent) = ancestors.parent(node) {
$count_unary(&parent, &mut stats.conditions);
}
}
IfStatement | WhileStatement => {
if let Some(cond) = node.child(1) {
$inspect_container(&cond, node, &mut stats.conditions);
}
}
DoStatement => {
if let Some(cond) = node.child(3) {
$inspect_container(&cond, node, &mut stats.conditions);
}
}
ReturnStatement => {
if let Some(value) = node.child(1) {
$inspect_container(&value, node, &mut stats.conditions);
}
}
Arguments => {
$count_unary(node, &mut stats.conditions);
}
TernaryExpression => {
$walk_ternary(node, &mut stats.conditions);
}
ForStatement => {
$walk_for(node, &mut stats.conditions);
}
_ => {}
}
}
};
}
impl Abc for TypescriptCode {
ts_abc_compute!(
Typescript,
typescript_count_unary_conditions,
typescript_inspect_container,
typescript_walk_ternary,
typescript_walk_for_statement,
typescript_eq_initializes_const_binding
);
}
impl Abc for TsxCode {
ts_abc_compute!(
Tsx,
tsx_count_unary_conditions,
tsx_inspect_container,
tsx_walk_ternary,
tsx_walk_for_statement,
tsx_eq_initializes_const_binding
);
}
macro_rules! js_abc_compute {
(
$lang:ident,
$count_unary:path,
$inspect_container:path,
$walk_ternary:path,
$walk_for:path,
$const_binding:path
) => {
fn compute<'a>(
node: &Node<'a>,
_code: &'a [u8],
ancestors: Ancestors<'a, '_>,
stats: &mut Stats,
) {
use $lang::*;
match node.kind_id().into() {
PLUSEQ | DASHEQ | STAREQ | SLASHEQ | PERCENTEQ | STARSTAREQ | AMPEQ | PIPEEQ
| CARETEQ | LTLTEQ | GTGTEQ | GTGTGTEQ | AMPAMPEQ | PIPEPIPEEQ | QMARKQMARKEQ
| PLUSPLUS | DASHDASH => {
stats.assignments += 1.;
}
EQ if !$const_binding(node, ancestors) => {
stats.assignments += 1.;
}
CallExpression | NewExpression => {
stats.branches += 1.;
}
EQEQ | EQEQEQ | BANGEQ | BANGEQEQ | LTEQ | GTEQ | LT | GT | QMARK | QMARKQMARK
| Instanceof | Else | Case | Try | Catch => {
stats.conditions += 1.;
}
AMPAMP | PIPEPIPE => {
if let Some(parent) = ancestors.parent(node) {
$count_unary(&parent, &mut stats.conditions);
}
}
IfStatement | WhileStatement => {
if let Some(cond) = node.child(1) {
$inspect_container(&cond, node, &mut stats.conditions);
}
}
DoStatement => {
if let Some(cond) = node.child(3) {
$inspect_container(&cond, node, &mut stats.conditions);
}
}
ReturnStatement => {
if let Some(value) = node.child(1) {
$inspect_container(&value, node, &mut stats.conditions);
}
}
Arguments => {
$count_unary(node, &mut stats.conditions);
}
TernaryExpression => {
$walk_ternary(node, &mut stats.conditions);
}
ForStatement => {
$walk_for(node, &mut stats.conditions);
}
_ => {}
}
}
};
}
impl Abc for JavascriptCode {
js_abc_compute!(
Javascript,
javascript_count_unary_conditions,
javascript_inspect_container,
javascript_walk_ternary,
javascript_walk_for_statement,
javascript_eq_initializes_const_binding
);
}
impl Abc for MozjsCode {
js_abc_compute!(
Mozjs,
mozjs_count_unary_conditions,
mozjs_inspect_container,
mozjs_walk_ternary,
mozjs_walk_for_statement,
mozjs_eq_initializes_const_binding
);
}