use crate::findings::types::Finding;
use std::path::Path;
use tree_sitter::{Node, Tree};
use super::LongFunctionPolicy;
pub(super) fn detect_ast(
tree: &Tree,
content: &str,
language: &str,
path: &Path,
policy: LongFunctionPolicy,
) -> Vec<Finding> {
let mut findings = Vec::new();
visit(
tree.root_node(),
content,
language,
path,
policy,
&mut findings,
);
findings
}
fn visit(
node: Node<'_>,
content: &str,
language: &str,
path: &Path,
policy: LongFunctionPolicy,
findings: &mut Vec<Finding>,
) {
if let Some((name, is_anonymous)) = function_like(node, language, content) {
let start_row = node.start_position().row;
let end_row = node.end_position().row;
let fn_len = end_row.saturating_sub(start_row) + 1;
let threshold = if is_anonymous {
policy.threshold.saturating_mul(2)
} else {
policy.threshold
};
if fn_len > threshold {
let effective = LongFunctionPolicy {
threshold,
..policy
};
findings.push(super::build_finding(
path,
start_row + 1,
end_row + 1,
&name,
fn_len,
effective,
));
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
visit(child, content, language, path, policy, findings);
}
}
fn function_like(node: Node<'_>, language: &str, content: &str) -> Option<(String, bool)> {
match language {
"Rust" => (node.kind() == "function_item")
.then(|| (field_name(node, content).unwrap_or_default(), false)),
"Python" => (node.kind() == "function_definition")
.then(|| (field_name(node, content).unwrap_or_default(), false)),
"Go" => match node.kind() {
"function_declaration" | "method_declaration" => {
Some((field_name(node, content).unwrap_or_default(), false))
}
_ => None,
},
"Java" => match node.kind() {
"method_declaration" | "constructor_declaration" => {
Some((field_name(node, content).unwrap_or_default(), false))
}
_ => None,
},
"CSharp" | "C#" => match node.kind() {
"method_declaration" | "constructor_declaration" | "local_function_statement" => {
Some((field_name(node, content).unwrap_or_default(), false))
}
_ => None,
},
"Kotlin" => (node.kind() == "function_declaration")
.then(|| (field_name(node, content).unwrap_or_default(), false)),
_ => match node.kind() {
"function_declaration" | "generator_function_declaration" | "method_definition" => {
Some((field_name(node, content).unwrap_or_default(), false))
}
"function_expression" | "generator_function" => match field_name(node, content) {
Some(name) => Some((name, false)),
None => Some((String::new(), true)),
},
"arrow_function" => match arrow_function_name(node, content) {
Some(name) => Some((name, false)),
None => Some((String::new(), true)),
},
_ => None,
},
}
}
fn field_name(node: Node<'_>, content: &str) -> Option<String> {
let name = node.child_by_field_name("name")?;
name.utf8_text(content.as_bytes()).ok().map(str::to_string)
}
fn arrow_function_name(node: Node<'_>, content: &str) -> Option<String> {
let parent = node.parent()?;
let field = match parent.kind() {
"variable_declarator" | "public_field_definition" | "field_definition" => "name",
"pair" => "key",
"assignment_expression" => "left",
_ => return None,
};
let name = parent.child_by_field_name(field)?;
name.utf8_text(content.as_bytes()).ok().map(str::to_string)
}