Initial upload with CI/CD workflow

This commit is contained in:
2026-01-30 22:12:53 +00:00
parent 616bb4ad9a
commit 95f82e84e0

View File

@@ -0,0 +1,173 @@
"""Complexity calculation module for CodeSnap."""
from pathlib import Path
from typing import Optional
class ComplexityCalculator:
"""Calculates cyclomatic complexity for source code."""
def __init__(
self, low_threshold: int = 10, medium_threshold: int = 20, high_threshold: int = 50
) -> None:
self.low_threshold = low_threshold
self.medium_threshold = medium_threshold
self.high_threshold = high_threshold
def calculate(self, content: str, language: str = "python") -> int:
"""Calculate cyclomatic complexity for a file."""
complexity = 1
branching_keywords = self._get_branching_keywords(language)
for keyword in branching_keywords:
complexity += content.count(keyword)
lines = content.split("\n")
max_nesting = self._calculate_max_nesting(lines, language)
complexity += max_nesting // 2
long_functions = self._count_long_functions(lines, language)
complexity += long_functions
return complexity
def _get_branching_keywords(self, language: str) -> list[str]:
"""Get branching keywords for a language."""
common_keywords = ["if ", "elif ", "else:", "for ", "while ", "except:", "finally:"]
language_keywords = {
"python": common_keywords + ["case ", "match "],
"javascript": common_keywords
+ [
"switch (",
"case ",
"try {",
"catch (",
"&&",
"||",
"??",
],
"typescript": common_keywords
+ [
"switch (",
"case ",
"try {",
"catch (",
"&&",
"||",
"??",
],
"go": ["if ", "else {", "for ", "switch ", "case ", "default:"],
"rust": ["if ", "else", "match ", "loop ", "while ", "match "],
}
return language_keywords.get(language, common_keywords)
def _calculate_max_nesting(self, lines: list[str], language: str) -> int:
"""Calculate maximum nesting depth in the code."""
max_nesting = 0
current_nesting = 0
indent_char = "\t" if language in ("python", "yaml") else " "
for line in lines:
stripped = line.strip()
if not stripped or stripped.startswith("#") or stripped.startswith("//"):
continue
indent = len(line) - len(line.lstrip(indent_char))
new_nesting = indent // (4 if indent_char == " " else 1)
if new_nesting > current_nesting:
current_nesting = new_nesting
max_nesting = max(max_nesting, current_nesting)
return max_nesting
def _count_long_functions(self, lines: list[str], language: str) -> int:
"""Count functions that exceed length threshold."""
threshold = 50
count = 0
in_function = False
func_start = 0
for i, line in enumerate(lines):
if language == "python":
if line.strip().startswith("def "):
if in_function:
func_len = i - func_start
if func_len > threshold:
count += 1
in_function = True
func_start = i
elif language in ("javascript", "typescript"):
if "function" in line or "=>" in line:
if in_function:
func_len = i - func_start
if func_len > threshold:
count += 1
in_function = True
func_start = i
if in_function:
func_len = len(lines) - func_start
if func_len > threshold:
count += 1
return count
def get_rating(self, complexity: int) -> str:
"""Get complexity rating string."""
if complexity <= self.low_threshold:
return "low"
elif complexity <= self.medium_threshold:
return "medium"
elif complexity <= self.high_threshold:
return "high"
else:
return "critical"
def calculate_file_complexity(
self, file_path: Path, content: Optional[str] = None
) -> dict[str, any]:
"""Calculate complexity for a single file."""
if content is None:
try:
content = file_path.read_text(encoding="utf-8")
except Exception:
return {
"path": str(file_path),
"complexity": 0,
"rating": "unknown",
"error": "Could not read file",
}
language = self._detect_language(file_path)
complexity = self.calculate(content, language)
return {
"path": str(file_path),
"complexity": complexity,
"rating": self.get_rating(complexity),
"language": language,
}
def _detect_language(self, file_path: Path) -> str:
"""Detect language from file extension."""
suffix = file_path.suffix.lower()
language_map = {
".py": "python",
".js": "javascript",
".ts": "typescript",
".go": "go",
".rs": "rust",
".java": "java",
}
return language_map.get(suffix, "python")
def calculate_batch(
self, files: list[Path], contents: dict[str, str]
) -> dict[str, dict[str, any]]:
"""Calculate complexity for multiple files."""
results = {}
for file_path in files:
content = contents.get(str(file_path))
results[str(file_path)] = self.calculate_file_complexity(file_path, content)
return results