Add graph, analyzers, and exporters modules
Some checks failed
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled

This commit is contained in:
2026-02-02 02:40:23 +00:00
parent fbecb42d6c
commit 84cc89a503

165
src/analyzers/complexity.py Normal file
View File

@@ -0,0 +1,165 @@
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional
from src.parsers.base import Entity, EntityType
@dataclass
class ComplexityReport:
file_path: Optional[Path] = None
functions: list[dict] = field(default_factory=list)
classes: list[dict] = field(default_factory=list)
total_cyclomatic_complexity: int = 0
average_complexity: float = 0.0
high_complexity_functions: list[dict] = field(default_factory=list)
warnings: list[str] = field(default_factory=list)
class ComplexityCalculator:
DECISION_POINTS = {"if_statement", "elif_statement", "else_clause", "for_statement",
"while_statement", "case_clause", "ternary_expression"}
def __init__(self):
self.complexity_threshold = 10
def calculate_for_entity(self, entity: Entity) -> dict:
code = entity.code
base_complexity = 1
for_decision_points = self._count_decision_points(code)
complexity = base_complexity + for_decision_points
return {
"name": entity.name,
"entity_type": entity.entity_type.value,
"file_path": str(entity.file_path),
"start_line": entity.start_line,
"end_line": entity.end_line,
"complexity_score": complexity,
"decision_points": for_decision_points,
"lines_of_code": entity.end_line - entity.start_line + 1,
"is_complex": complexity > self.complexity_threshold,
}
def _count_decision_points(self, code: str) -> int:
count = 0
if "if " in code:
if_count = code.count("if ")
if_count += code.count("elif ")
count += if_count
if " for " in code or "for " in code:
count += code.count("for ")
if " while " in code or "while " in code:
count += code.count("while ")
if " case " in code or "case:" in code:
count += code.count("case ")
count += code.count("? ") if "?" in code else 0
count += code.count(" and ") if " and " in code else 0
count += code.count(" or ") if " or " in code else 0
return count
def calculate_for_file(self, file_path: Path, entities: list[Entity]) -> ComplexityReport:
report = ComplexityReport(file_path=file_path)
for entity in entities:
if entity.entity_type in [EntityType.FUNCTION, EntityType.METHOD]:
entity_complexity = self.calculate_for_entity(entity)
report.functions.append(entity_complexity)
report.total_cyclomatic_complexity += entity_complexity["complexity_score"]
if entity_complexity["is_complex"]:
report.high_complexity_functions.append(entity_complexity)
elif entity.entity_type == EntityType.CLASS:
class_info = {
"name": entity.name,
"file_path": str(entity.file_path),
"start_line": entity.start_line,
"end_line": entity.end_line,
"methods_count": len(entity.children),
"total_complexity": sum(
self.calculate_for_entity(child)["complexity_score"]
for child in entity.children
if child.entity_type == EntityType.METHOD
),
}
report.classes.append(class_info)
report.total_cyclomatic_complexity += class_info["total_complexity"]
if report.functions:
complexities = [f["complexity_score"] for f in report.functions]
report.average_complexity = sum(complexities) / len(complexities)
if report.high_complexity_functions:
report.warnings.append(
f"Found {len(report.high_complexity_functions)} functions with high complexity"
)
return report
def calculate_project_complexity(self, entities: list[Entity]) -> dict:
function_complexities = []
class_complexities = []
for entity in entities:
if entity.entity_type in [EntityType.FUNCTION, EntityType.METHOD]:
complexity = self.calculate_for_entity(entity)
function_complexities.append(complexity)
elif entity.entity_type == EntityType.CLASS:
class_info = {
"name": entity.name,
"file_path": str(entity.file_path),
"methods": [
self.calculate_for_entity(child)
for child in entity.children
if child.entity_type == EntityType.METHOD
],
}
class_complexities.append(class_info)
total_complexity = sum(f["complexity_score"] for f in function_complexities)
avg_complexity = (
total_complexity / len(function_complexities)
if function_complexities
else 0
)
return {
"total_functions": len(function_complexities),
"total_classes": len(class_complexities),
"total_cyclomatic_complexity": total_complexity,
"average_complexity": round(avg_complexity, 2),
"high_complexity_count": sum(1 for f in function_complexities if f["is_complex"]),
"functions": function_complexities[:20],
"complexity_distribution": self._get_complexity_distribution(function_complexities),
}
def _get_complexity_distribution(self, complexities: list[dict]) -> dict:
distribution = {"low": 0, "medium": 0, "high": 0, "very_high": 0}
for c in complexities:
score = c["complexity_score"]
if score <= 5:
distribution["low"] += 1
elif score <= 10:
distribution["medium"] += 1
elif score <= 20:
distribution["high"] += 1
else:
distribution["very_high"] += 1
return distribution
def set_complexity_threshold(self, threshold: int) -> None:
self.complexity_threshold = threshold