#![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, last_operand, wrapped_operand,
};
use crate::*;
fn js_family_wrapper_operand<'a>(node: &Node<'a>) -> Option<(Node<'a>, bool)> {
match node.kind() {
"parenthesized_expression"
| "await_expression"
| "as_expression"
| "satisfies_expression"
| "non_null_expression" => wrapped_operand(node).map(|o| (o, false)),
"sequence_expression" | "type_assertion" => last_operand(node).map(|o| (o, false)),
"assignment_expression" => node.child_by_field_name("right").map(|o| (o, false)),
"unary_expression" => node
.child_by_field_name("operator")
.filter(|op| op.kind() == "!")
.and_then(|_| node.child_by_field_name("argument"))
.map(|o| (o, true)),
_ => None,
}
}
fn js_family_condition_scores_itself(expr: &Node) -> bool {
match expr.kind() {
"ternary_expression" => true,
"binary_expression" => expr.child_by_field_name("operator").is_some_and(|op| {
matches!(
op.kind(),
"==" | "==="
| "!="
| "!=="
| "<"
| ">"
| "<="
| ">="
| "??"
| "instanceof"
| "&&"
| "||"
)
}),
_ => false,
}
}
fn js_family_count_condition(condition: &Node, conditions: &mut f64) {
count_boolean_slot(
condition,
js_family_wrapper_operand,
js_family_condition_scores_itself,
conditions,
);
}
fn js_family_count_negated(operand: &Node, conditions: &mut f64) {
count_negated_operand(
operand,
js_family_wrapper_operand,
js_family_condition_scores_itself,
conditions,
);
}
fn js_family_walk_ternary(node: &Node, conditions: &mut f64) {
if let Some(condition) = node.child_by_field_name("condition") {
js_family_count_condition(&condition, conditions);
}
for field in ["consequence", "alternative"] {
if let Some(branch) = node.child_by_field_name(field) {
js_family_count_negated(&branch, conditions);
}
}
}
fn js_family_walk_for(node: &Node, conditions: &mut f64) {
if let Some(condition) = node
.child_by_field_name("condition")
.filter(|slot| slot.is_named() && slot.kind() != "empty_statement")
{
js_family_count_condition(&condition, conditions);
}
}
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,
$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_has_kind(node, BinaryExpression as u16) => {
stats.conditions += 1.;
}
AMPAMP | PIPEPIPE => {
if let Some(chain) = ancestors.parent(node) {
count_field_operands(
&chain,
js_family_count_condition,
&mut stats.conditions,
);
}
}
IfStatement | WhileStatement | DoStatement => {
if let Some(cond) = node.child_by_field_name("condition") {
js_family_count_condition(&cond, &mut stats.conditions);
}
}
ReturnStatement => {
if let Some(value) = wrapped_operand(node) {
js_family_count_negated(&value, &mut stats.conditions);
}
}
Arguments => {
count_each_operand(node, js_family_count_negated, &mut stats.conditions);
}
TernaryExpression => {
js_family_walk_ternary(node, &mut stats.conditions);
}
ForStatement => {
js_family_walk_for(node, &mut stats.conditions);
}
_ => {}
}
}
};
}
impl Abc for TypescriptCode {
ts_abc_compute!(Typescript, typescript_eq_initializes_const_binding);
}
impl Abc for TsxCode {
ts_abc_compute!(Tsx, tsx_eq_initializes_const_binding);
}
macro_rules! js_abc_compute {
(
$lang:ident,
$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 | QMARK | QMARKQMARK
| Instanceof | Else | Case | Try | Catch => {
stats.conditions += 1.;
}
GT | LT if ancestors.parent_has_kind(node, BinaryExpression as u16) => {
stats.conditions += 1.;
}
AMPAMP | PIPEPIPE => {
if let Some(chain) = ancestors.parent(node) {
count_field_operands(
&chain,
js_family_count_condition,
&mut stats.conditions,
);
}
}
IfStatement | WhileStatement | DoStatement => {
if let Some(cond) = node.child_by_field_name("condition") {
js_family_count_condition(&cond, &mut stats.conditions);
}
}
ReturnStatement => {
if let Some(value) = wrapped_operand(node) {
js_family_count_negated(&value, &mut stats.conditions);
}
}
Arguments => {
count_each_operand(node, js_family_count_negated, &mut stats.conditions);
}
TernaryExpression => {
js_family_walk_ternary(node, &mut stats.conditions);
}
ForStatement => {
js_family_walk_for(node, &mut stats.conditions);
}
_ => {}
}
}
};
}
impl Abc for JavascriptCode {
js_abc_compute!(Javascript, javascript_eq_initializes_const_binding);
}
impl Abc for MozjsCode {
js_abc_compute!(Mozjs, mozjs_eq_initializes_const_binding);
}