use crate::languages::LanguageProfile;
use tree_sitter::Node;
pub fn compute_cognitive(
node: &Node,
source: &[u8],
profile: &dyn LanguageProfile,
function_name: Option<&str>,
) -> u64 {
let mut complexity: u64 = 0;
walk_cognitive(node, source, profile, 0, function_name, &mut complexity);
complexity
}
fn walk_cognitive(
node: &Node,
source: &[u8],
profile: &dyn LanguageProfile,
nesting: u64,
function_name: Option<&str>,
complexity: &mut u64,
) {
let kind = node.kind();
let control_flow = profile.control_flow_nodes();
let nesting_nodes = profile.nesting_nodes();
let else_if = profile.else_if_nodes();
let lambda = profile.lambda_nodes();
if let Some(fn_name) = function_name
&& is_recursive_call(node, source, fn_name, profile)
{
*complexity += 1;
}
if let Some(op) = get_boolean_op(node, source, profile) {
let parent_op = node
.parent()
.and_then(|p| get_boolean_op(&p, source, profile));
if parent_op.is_none() {
*complexity += 1;
*complexity += count_expression_switches(node, source, profile, &op);
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if get_boolean_op(&child, source, profile).is_none() {
walk_cognitive(&child, source, profile, nesting, function_name, complexity);
}
}
return;
}
if control_flow.contains(&kind) {
if else_if.contains(&kind) {
*complexity += 1;
} else if nesting_nodes.contains(&kind) {
*complexity += 1 + nesting;
} else {
*complexity += 1;
}
}
let child_nesting =
if (nesting_nodes.contains(&kind) && !else_if.contains(&kind)) || lambda.contains(&kind) {
nesting + 1
} else {
nesting
};
if profile.function_nodes().contains(&kind) && nesting > 0 {
return;
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
walk_cognitive(
&child,
source,
profile,
child_nesting,
function_name,
complexity,
);
}
}
fn get_boolean_op(node: &Node, source: &[u8], profile: &dyn LanguageProfile) -> Option<String> {
if !profile.boolean_expression_nodes().contains(&node.kind()) {
return None;
}
let boolean_ops = profile.boolean_operators();
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if boolean_ops.contains(&child.kind()) {
return child.utf8_text(source).ok().map(|s| s.to_string());
}
}
None
}
fn count_expression_switches(
node: &Node,
source: &[u8],
profile: &dyn LanguageProfile,
current_op: &str,
) -> u64 {
let mut switches = 0u64;
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if let Some(child_op) = get_boolean_op(&child, source, profile) {
if child_op != current_op {
switches += 1;
}
switches += count_expression_switches(&child, source, profile, &child_op);
}
}
switches
}
fn is_recursive_call(
node: &Node,
source: &[u8],
function_name: &str,
profile: &dyn LanguageProfile,
) -> bool {
if profile.call_nodes().contains(&node.kind())
&& let Some(func_node) = node.child_by_field_name(profile.call_function_field())
{
let text = func_node.utf8_text(source).unwrap_or("");
return text == function_name;
}
false
}