use super::super::types::{FunctionInfo, StructureInfo, StructureType, Visibility};
use super::LanguageAnalyzer;
use crate::utils::errors::Result;
pub struct JavaScriptAnalyzer;
impl JavaScriptAnalyzer {
pub fn new() -> Self {
Self
}
fn is_function_declaration(&self, line: &str) -> bool {
line.contains("function ")
|| line.contains("=> ")
|| (line.contains("(") && line.contains(")") && line.contains("{"))
}
fn extract_function_name(&self, line: &str) -> Option<String> {
if line.contains("function ") {
if let Some(start) = line.find("function ") {
let after_function = &line[start + 9..];
if let Some(end) = after_function.find('(') {
return Some(after_function[..end].trim().to_string());
}
}
}
if let Some(arrow_pos) = line.find("=>") {
let before_arrow = &line[..arrow_pos];
if let Some(equals_pos) = before_arrow.rfind('=') {
let name_part = &before_arrow[..equals_pos];
if let Some(name) = name_part.split_whitespace().last() {
return Some(name.to_string());
}
}
}
Some("anonymous".to_string())
}
fn count_complexity_keywords(&self, line: &str) -> usize {
let keywords = [
"if", "else if", "while", "for", "switch", "case", "catch", "finally", "&&", "||", "?",
];
keywords
.iter()
.map(|&keyword| line.matches(keyword).count())
.sum()
}
fn count_cognitive_complexity(&self, line: &str, nesting_level: i32) -> usize {
let mut complexity = 0;
let nesting_multiplier = (nesting_level as usize).max(1);
if line.contains("if") {
complexity += nesting_multiplier;
}
if line.contains("else") {
complexity += 1;
}
if line.contains("while") {
complexity += nesting_multiplier;
}
if line.contains("for") {
complexity += nesting_multiplier;
}
if line.contains("switch") {
complexity += nesting_multiplier;
}
if line.contains("case") {
complexity += 1;
}
if line.contains("catch") {
complexity += 1;
}
if line.contains("finally") {
complexity += 1;
}
complexity += line.matches("&&").count() * nesting_multiplier;
complexity += line.matches("||").count() * nesting_multiplier;
complexity += line.matches("?").count() * nesting_multiplier;
if line.contains("(") && line.contains(")") {
complexity += line.matches("()").count().min(1);
}
complexity
}
fn count_function_parameters(&self, line: &str) -> usize {
if let Some(start) = line.find('(') {
if let Some(end) = line.rfind(')') {
if end > start {
let params_str = &line[start + 1..end];
if params_str.trim().is_empty() {
return 0;
}
let param_count = params_str.split(',').count();
if params_str.contains("this") {
return param_count.saturating_sub(1);
}
return param_count;
}
}
}
0
}
fn detect_structure(&self, line: &str) -> Option<(StructureType, String, Visibility)> {
let visibility = if line.contains("export ") {
Visibility::Public
} else {
Visibility::Private
};
if line.contains("class ") {
if let Some(name) = self.extract_class_name(line) {
return Some((StructureType::Class, name, visibility));
}
}
if line.contains("interface ") {
if let Some(name) = self.extract_structure_name(line, "interface ") {
return Some((StructureType::Interface, name, visibility));
}
}
if line.contains("enum ") {
if let Some(name) = self.extract_structure_name(line, "enum ") {
return Some((StructureType::Enum, name, visibility));
}
}
None
}
fn extract_class_name(&self, line: &str) -> Option<String> {
if let Some(start) = line.find("class ") {
let after_class = &line[start + 6..];
let name_part = after_class.split_whitespace().next()?;
let name = name_part.split('{').next()?.split('(').next()?.trim();
if !name.is_empty() {
Some(name.to_string())
} else {
None
}
} else {
None
}
}
fn extract_structure_name(&self, line: &str, keyword: &str) -> Option<String> {
if let Some(start) = line.find(keyword) {
let after_keyword = &line[start + keyword.len()..];
let name_part = after_keyword.split_whitespace().next()?;
let name = name_part.split('<').next()?.split('{').next()?.trim();
if !name.is_empty() {
Some(name.to_string())
} else {
None
}
} else {
None
}
}
}
impl LanguageAnalyzer for JavaScriptAnalyzer {
fn analyze_functions(&self, lines: &[String]) -> Result<Vec<FunctionInfo>> {
let mut functions = Vec::new();
let mut current_function: Option<FunctionInfo> = None;
let mut brace_count = 0;
let mut in_function = false;
for (line_num, line) in lines.iter().enumerate() {
let trimmed = line.trim();
if trimmed.starts_with("//") || trimmed.starts_with("/*") || trimmed.is_empty() {
continue;
}
if self.is_function_declaration(trimmed) {
if let Some(func_name) = self.extract_function_name(trimmed) {
current_function = Some(FunctionInfo {
name: func_name,
line_count: 0,
cyclomatic_complexity: 1, cognitive_complexity: 1, nesting_depth: 0,
parameter_count: 0,
return_path_count: 0,
start_line: line_num + 1,
end_line: line_num + 1,
is_method: false,
parent_class: None,
local_variable_count: 0,
has_recursion: false,
has_exception_handling: false,
visibility: Visibility::Public,
});
in_function = true;
brace_count = 0;
}
}
if in_function {
if let Some(ref mut func) = current_function {
func.line_count += 1;
func.end_line = line_num + 1;
let open_braces = trimmed.matches('{').count();
let close_braces = trimmed.matches('}').count();
brace_count += open_braces as i32 - close_braces as i32;
func.nesting_depth = func.nesting_depth.max(brace_count.max(0) as usize);
func.cyclomatic_complexity += self.count_complexity_keywords(trimmed);
func.cognitive_complexity +=
self.count_cognitive_complexity(trimmed, brace_count);
if trimmed.contains('(') && func.parameter_count == 0 {
func.parameter_count = self.count_function_parameters(trimmed);
}
if trimmed.contains("return") {
func.return_path_count += 1;
}
if trimmed.contains(&func.name) && !trimmed.starts_with("function ") {
func.has_recursion = true;
}
if trimmed.contains("try")
|| trimmed.contains("catch")
|| trimmed.contains("throw")
{
func.has_exception_handling = true;
}
if brace_count <= 0 && close_braces > 0 {
functions.push(func.clone());
current_function = None;
in_function = false;
}
}
}
}
Ok(functions)
}
fn analyze_structures(&self, lines: &[String]) -> Result<Vec<StructureInfo>> {
let mut structures = Vec::new();
let mut current_structure: Option<StructureInfo> = None;
let mut brace_count = 0;
let mut in_structure = false;
for (line_num, line) in lines.iter().enumerate() {
let trimmed = line.trim();
if trimmed.starts_with("//") || trimmed.starts_with("/*") || trimmed.is_empty() {
continue;
}
if let Some((structure_type, name, visibility)) = self.detect_structure(trimmed) {
current_structure = Some(StructureInfo {
name,
structure_type,
line_count: 0,
start_line: line_num + 1,
end_line: line_num + 1,
methods: Vec::new(),
properties: 0,
visibility,
inheritance_depth: 0,
interface_count: 0,
});
in_structure = true;
brace_count = 0;
}
if in_structure {
if let Some(ref mut structure) = current_structure {
structure.line_count += 1;
structure.end_line = line_num + 1;
let open_braces = trimmed.matches('{').count();
let close_braces = trimmed.matches('}').count();
brace_count += open_braces as i32 - close_braces as i32;
if (trimmed.contains("this.") && trimmed.contains('='))
|| (trimmed.contains(':')
&& !trimmed.contains("function")
&& !trimmed.contains("=>"))
{
structure.properties += 1;
}
if brace_count <= 0 && close_braces > 0 {
structures.push(structure.clone());
current_structure = None;
in_structure = false;
}
}
}
}
Ok(structures)
}
}
impl Default for JavaScriptAnalyzer {
fn default() -> Self {
Self::new()
}
}