use super::super::types::{FunctionInfo, StructureInfo, StructureType, Visibility};
use super::LanguageAnalyzer;
use crate::utils::errors::Result;
pub struct ElixirAnalyzer;
impl ElixirAnalyzer {
pub fn new() -> Self {
Self
}
fn extract_function_name(&self, line: &str) -> Option<String> {
let trimmed = line.trim();
if trimmed.starts_with("#") || trimmed.is_empty() {
return None;
}
if let Some(start) = trimmed.find("def ").or_else(|| trimmed.find("defp ")) {
let offset = if trimmed[start..].starts_with("defp ") {
5
} else {
4
};
let after_def = &trimmed[start + offset..];
let func_part = after_def.trim();
let end_pos = func_part
.find('(')
.or_else(|| func_part.find(','))
.or_else(|| func_part.find(" do"))
.or_else(|| func_part.find(" when"))
.unwrap_or(func_part.len());
let func_name = &func_part[..end_pos].trim();
if !func_name.is_empty()
&& func_name
.chars()
.all(|c| c.is_alphanumeric() || c == '_' || c == '?' || c == '!')
{
return Some(func_name.to_string());
}
}
if trimmed.contains("fn ") {
return Some("anonymous".to_string());
}
None
}
fn extract_structure_name(&self, line: &str) -> Option<String> {
let trimmed = line.trim();
if let Some(start) = trimmed.find("defmodule ") {
let after_defmodule = &trimmed[start + 10..];
let parts: Vec<&str> = after_defmodule.split_whitespace().collect();
if let Some(first_part) = parts.first() {
let name = if let Some(do_pos) = first_part.find(" do") {
&first_part[..do_pos]
} else {
first_part
};
if !name.is_empty()
&& name
.chars()
.all(|c| c.is_alphanumeric() || c == '_' || c == '.')
{
return Some(name.to_string());
}
}
}
if trimmed.contains("defstruct ") {
return Some("struct".to_string());
}
if let Some(start) = trimmed.find("defprotocol ") {
let after_defprotocol = &trimmed[start + 12..];
let parts: Vec<&str> = after_defprotocol.split_whitespace().collect();
if let Some(first_part) = parts.first() {
let name = if let Some(do_pos) = first_part.find(" do") {
&first_part[..do_pos]
} else {
first_part
};
if !name.is_empty()
&& name
.chars()
.all(|c| c.is_alphanumeric() || c == '_' || c == '.')
{
return Some(name.to_string());
}
}
}
None
}
fn count_complexity_keywords(&self, line: &str) -> usize {
let keywords = [
"if", "unless", "cond", "case", "when", "for", "while", "until", "&&", "||", "and",
"or", "not", "try", "rescue", "catch", "after", "receive", "with", "else", "->", "|>",
"spawn", "send",
];
keywords
.iter()
.map(|&keyword| line.matches(keyword).count())
.sum()
}
fn calculate_cyclomatic_complexity(
&self,
lines: &[String],
start_line: usize,
end_line: usize,
) -> usize {
let mut complexity = 1;
for line in lines.iter().take(end_line + 1).skip(start_line) {
complexity += self.count_complexity_keywords(line);
}
complexity
}
fn find_function_end(&self, lines: &[String], start_line: usize) -> usize {
let mut depth = 0;
let mut in_function = false;
for (i, line) in lines.iter().enumerate().skip(start_line) {
let trimmed = line.trim();
if trimmed.contains("def ") || trimmed.contains("defp ") {
in_function = true;
}
if in_function {
if trimmed.contains(" do") || trimmed.ends_with(" do") {
depth += 1;
}
if trimmed == "end" || trimmed.starts_with("end ") {
depth -= 1;
if depth == 0 {
return i;
}
}
if trimmed.contains(", do:") && depth == 0 {
return i;
}
}
}
lines.len().saturating_sub(1)
}
fn determine_visibility(&self, line: &str) -> Visibility {
if line.trim().contains("defp ") {
Visibility::Private
} else {
Visibility::Public }
}
fn determine_structure_type(&self, line: &str) -> StructureType {
let trimmed = line.trim();
if trimmed.contains("defmodule ") {
StructureType::Module
} else if trimmed.contains("defprotocol ") {
StructureType::Interface
} else if trimmed.contains("defstruct ") {
StructureType::Struct
} else {
StructureType::Class }
}
}
impl LanguageAnalyzer for ElixirAnalyzer {
fn analyze_functions(&self, lines: &[String]) -> Result<Vec<FunctionInfo>> {
let mut functions = Vec::new();
for (i, line) in lines.iter().enumerate() {
if let Some(func_name) = self.extract_function_name(line) {
let end_line = self.find_function_end(lines, i);
let complexity = self.calculate_cyclomatic_complexity(lines, i, end_line);
let _visibility = self.determine_visibility(line);
functions.push(FunctionInfo {
name: func_name,
line_count: end_line.saturating_sub(i).max(1),
cyclomatic_complexity: complexity,
cognitive_complexity: complexity, nesting_depth: 0, parameter_count: self.count_parameters(line),
return_path_count: 1, start_line: i + 1,
end_line: end_line + 1,
is_method: false,
parent_class: None,
local_variable_count: 0,
has_recursion: false,
has_exception_handling: false,
visibility: Visibility::Public,
});
}
}
Ok(functions)
}
fn analyze_structures(&self, lines: &[String]) -> Result<Vec<StructureInfo>> {
let mut structures = Vec::new();
for (i, line) in lines.iter().enumerate() {
if let Some(struct_name) = self.extract_structure_name(line) {
let end_line = self.find_structure_end(lines, i);
let structure_type = self.determine_structure_type(line);
let _visibility = Visibility::Public;
structures.push(StructureInfo {
name: struct_name,
structure_type,
line_count: end_line.saturating_sub(i).max(1),
start_line: i + 1,
end_line: end_line + 1,
methods: self.collect_methods_in_structure(lines, i, end_line),
properties: self.count_fields_in_structure(lines, i, end_line),
visibility: Visibility::Public,
inheritance_depth: 0,
interface_count: 0,
});
}
}
Ok(structures)
}
}
impl ElixirAnalyzer {
fn count_parameters(&self, line: &str) -> usize {
if let Some(start) = line.find('(') {
if let Some(end) = line.find(')') {
let params = &line[start + 1..end];
if params.trim().is_empty() {
return 0;
}
return params.split(',').count();
}
}
if line.contains("def ") || line.contains("defp ") {
let after_def = if let Some(pos) = line.find("def ") {
&line[pos + 4..]
} else if let Some(pos) = line.find("defp ") {
&line[pos + 5..]
} else {
return 0;
};
let args_part = if let Some(do_pos) = after_def.find(" do") {
&after_def[..do_pos]
} else if let Some(when_pos) = after_def.find(" when") {
&after_def[..when_pos]
} else {
after_def
};
if args_part.trim().is_empty() {
return 0;
}
return args_part.split(',').count();
}
0
}
fn find_structure_end(&self, lines: &[String], start_line: usize) -> usize {
let mut depth = 0;
let mut in_structure = false;
for (i, line) in lines.iter().enumerate().skip(start_line) {
let trimmed = line.trim();
if trimmed.contains("defmodule ") || trimmed.contains("defprotocol ") {
in_structure = true;
}
if in_structure {
if trimmed.contains(" do") || trimmed.ends_with(" do") {
depth += 1;
}
if trimmed == "end" || trimmed.starts_with("end ") {
depth -= 1;
if depth == 0 {
return i;
}
}
}
}
lines.len().saturating_sub(1)
}
fn collect_methods_in_structure(
&self,
lines: &[String],
start_line: usize,
end_line: usize,
) -> Vec<FunctionInfo> {
let mut methods = Vec::new();
for i in start_line..=end_line.min(lines.len().saturating_sub(1)) {
if let Some(func_name) = self.extract_function_name(&lines[i]) {
let func_end_line = self.find_function_end(lines, i);
let complexity = self.calculate_cyclomatic_complexity(lines, i, func_end_line);
methods.push(FunctionInfo {
name: func_name,
line_count: func_end_line.saturating_sub(i).max(1),
cyclomatic_complexity: complexity,
cognitive_complexity: complexity,
nesting_depth: 0,
parameter_count: self.count_parameters(&lines[i]),
return_path_count: 1,
start_line: i + 1,
end_line: func_end_line + 1,
is_method: true,
parent_class: None,
local_variable_count: 0,
has_recursion: false,
has_exception_handling: false,
visibility: Visibility::Public,
});
}
}
methods
}
fn count_fields_in_structure(
&self,
lines: &[String],
start_line: usize,
end_line: usize,
) -> usize {
let mut count = 0;
for line in lines.iter().take(end_line + 1).skip(start_line) {
if line.trim().contains("defstruct ") {
if let Some(start) = line.find("[") {
if let Some(end) = line.find("]") {
let fields = &line[start + 1..end];
count += fields.split(',').count();
}
}
}
if line.trim().starts_with("@") {
count += 1;
}
}
count
}
}
impl Default for ElixirAnalyzer {
fn default() -> Self {
Self::new()
}
}