use super::Extractor;
use crate::models::FunctionInfo;
use tree_sitter::{Language, Node, Parser, Query, QueryCursor, StreamingIterator};
use std::collections::HashMap;
const DECISION_NODE_TYPES: &[&str] = &[
"if_expression", "if_statement", "elif_clause", "elseif_clause", "while_expression", "while_statement", "do_statement", "for_expression", "for_statement", "for_in_statement", "for_of_statement", "foreach_statement", "enhanced_for_statement", "loop_expression", "match_arm", "case_statement", "switch_block_statement_group", "expression_case_clause", "case_clause", "when", "catch_clause", "except_clause", "rescue_clause", "rescue", "ternary_expression", "conditional_expression", "guard_statement", "boolean_operator", "logical_and", "logical_or", ];
const BINARY_EXPR_TYPES: &[&str] = &[
"binary_expression", "logical_expression", ];
const LOGICAL_OPS: &[&str] = &["&&", "||"];
pub fn ast_complexity(root: Node<'_>, src: &[u8]) -> u32 {
let mut cc = 1u32;
let mut cursor = root.walk();
let mut visited = false;
loop {
if !visited {
let node = cursor.node();
let kind = node.kind();
if DECISION_NODE_TYPES.contains(&kind) {
cc += 1;
} else if BINARY_EXPR_TYPES.contains(&kind) {
let child_count = node.child_count();
for i in 0..child_count {
if let Some(child) = node.child(i) {
let text = child.utf8_text(src).unwrap_or("");
if LOGICAL_OPS.contains(&text) {
cc += 1;
}
}
}
}
}
if !visited && cursor.goto_first_child() {
visited = false;
} else if cursor.goto_next_sibling() {
visited = false;
} else if cursor.goto_parent() {
visited = true;
if cursor.node().id() == root.id() {
break;
}
} else {
break;
}
}
cc
}
#[allow(dead_code)]
pub struct TreeSitterExtractor {
language: Language,
query: Query,
}
#[allow(dead_code)]
impl TreeSitterExtractor {
pub fn new(language: Language, query_source: &str) -> Result<Self, tree_sitter::QueryError> {
let query = Query::new(&language, query_source)?;
Ok(Self { language, query })
}
}
impl Extractor for TreeSitterExtractor {
fn extract(&self, content: &str) -> Vec<FunctionInfo> {
let mut parser = Parser::new();
if parser.set_language(&self.language).is_err() {
return vec![];
}
let tree = match parser.parse(content, None) {
Some(tree) => tree,
None => return vec![],
};
let mut cursor = QueryCursor::new();
let mut matches = cursor.matches(&self.query, tree.root_node(), content.as_bytes());
let mut functions = HashMap::new();
let capture_names = self.query.capture_names();
let mut capture_map = HashMap::new();
for (i, name) in capture_names.iter().enumerate() {
capture_map.insert(*name, i as u32);
}
while let Some(m) = matches.next() {
let mut root_node = None;
let mut name = String::new();
let mut is_class = false;
let mut is_method = false;
for cap in m.captures {
let capture_name = capture_names[cap.index as usize];
let text = cap.node.utf8_text(content.as_bytes()).unwrap_or("").to_string();
match capture_name {
"function" | "class" | "method" => {
root_node = Some(cap.node);
if capture_name == "class" {
is_class = true;
}
if capture_name == "method" {
is_method = true;
}
}
"name" => {
name = text;
}
_ => {}
}
}
if let Some(node) = root_node
&& !name.is_empty()
{
let start_point = node.start_position();
let end_point = node.end_position();
let line_start = start_point.row + 1;
let line_end = end_point.row + 1;
let complexity = if is_class { 1 } else { ast_complexity(node, content.as_bytes()) };
let info = FunctionInfo {
name: name.clone(),
line_start,
line_end,
parameters: vec![], is_async: false, is_method,
is_class,
docstring: None,
decorators: vec![],
complexity,
};
functions.insert(node.id(), info);
}
}
let mut result: Vec<_> = functions.into_values().collect();
result.sort_by_key(|f| f.line_start);
result
}
}