use tree_sitter::Node;
pub use git_topology::chunking::languages::{detect_language, SupportedLanguage};
use git_topology::chunking::parse;
fn cyclomatic_complexity(src: &str, tree: &tree_sitter::Tree) -> usize {
let mut count = 0usize;
let mut cursor = tree.walk();
loop {
let node = cursor.node();
match node.kind() {
"if_expression"
| "if_statement"
| "else_clause"
| "else_if_clause"
| "for_expression"
| "for_statement"
| "for_in_statement"
| "while_expression"
| "while_statement"
| "loop_expression"
| "match_arm"
| "catch_clause"
| "case_clause"
| "conditional_expression" => {
count += 1;
}
"binary_expression" | "logical_expression" => {
let op_node = node.child_by_field_name("operator");
let op_text = op_node
.map(|n: Node| n.utf8_text(src.as_bytes()).unwrap_or(""))
.unwrap_or("");
if matches!(op_text, "&&" | "||" | "and" | "or") {
count += 1;
}
}
_ => {}
}
if cursor.goto_first_child() {
continue;
}
loop {
if cursor.goto_next_sibling() {
break;
}
if !cursor.goto_parent() {
return count;
}
}
}
}
const FUNCTION_NODE_TYPES: &[&str] = &[
"function_item",
"function_declaration",
"function_definition",
"method_declaration",
"method_definition",
"impl_item",
];
const DOC_COMMENT_NODE_TYPES: &[&str] =
&["line_comment", "block_comment", "doc_comment", "comment"];
fn doc_gap(tree: &tree_sitter::Tree) -> f32 {
let mut total_fns = 0usize;
let mut undocumented = 0usize;
let mut cursor = tree.walk();
loop {
let node = cursor.node();
if FUNCTION_NODE_TYPES.contains(&node.kind()) {
total_fns += 1;
let parent = node.parent();
let has_doc = parent
.and_then(|p: Node| {
let idx = (0..p.child_count())
.find(|&i| p.child(i).map(|c: Node| c.id()) == Some(node.id()))?;
if idx == 0 {
return None;
}
p.child(idx - 1)
})
.map(|prev: Node| DOC_COMMENT_NODE_TYPES.contains(&prev.kind()))
.unwrap_or(false);
if !has_doc {
undocumented += 1;
}
}
if cursor.goto_first_child() {
continue;
}
loop {
if cursor.goto_next_sibling() {
break;
}
if !cursor.goto_parent() {
if total_fns == 0 {
return 0.0;
}
return undocumented as f32 / total_fns as f32;
}
}
}
}
pub fn complexity_delta(old_src: &str, new_src: &str, lang: &SupportedLanguage) -> f32 {
let old_tree = parse(old_src, *lang);
let new_tree = parse(new_src, *lang);
let old_complexity = old_tree
.as_ref()
.map(|t| cyclomatic_complexity(old_src, t))
.unwrap_or(0);
let new_complexity = new_tree
.as_ref()
.map(|t| cyclomatic_complexity(new_src, t))
.unwrap_or(0);
let added = new_complexity.saturating_sub(old_complexity);
let total = new_complexity.max(1);
(added as f32 / total as f32).clamp(0.0, 1.0)
}
pub fn doc_gap_score(new_src: &str, lang: &SupportedLanguage) -> f32 {
parse(new_src, *lang).as_ref().map(doc_gap).unwrap_or(0.0)
}