"""Complexity heuristics for Python code review.""" from __future__ import annotations import ast from typing import Any def _clamp_unit(value: float) -> float: return max(0.0, min(1.0, float(value))) def _estimate_time_complexity(loop_depth: int, uses_recursion: bool) -> str: if uses_recursion and loop_depth >= 1: return "O(n^2)" if loop_depth >= 3: return "O(n^3)" if loop_depth == 2: return "O(n^2)" if loop_depth == 1: return "O(n)" if uses_recursion: return "O(n)" return "O(1)" def _estimate_space_complexity(code: str, uses_recursion: bool) -> str: if uses_recursion: return "O(n)" if any(token in code for token in ("[]", "{}", "set(", "dict(", "list(", "Counter(")): return "O(n)" return "O(1)" def _cyclomatic_complexity(code: str) -> int: try: tree = ast.parse(code or "\n") except SyntaxError: return 1 decision_points = sum( isinstance(node, (ast.If, ast.For, ast.AsyncFor, ast.While, ast.Try, ast.ExceptHandler, ast.Match, ast.BoolOp)) for node in ast.walk(tree) ) return max(1, 1 + decision_points) def estimate_complexity(parsed: dict[str, Any], code: str) -> dict[str, Any]: """Estimate Python complexity signals from parsed structure plus source text.""" cyclomatic_complexity = _cyclomatic_complexity(code) loop_depth = int(parsed.get("max_loop_depth", 0) or 0) max_nesting_depth = int(parsed.get("max_nesting_depth", 0) or 0) uses_recursion = bool(parsed.get("uses_recursion", False)) line_count = int(parsed.get("line_count", 0) or 0) complexity_penalty = _clamp_unit( 0.08 + min(cyclomatic_complexity, 12) * 0.045 + min(loop_depth, 4) * 0.11 + min(max_nesting_depth, 4) * 0.06 + (0.06 if uses_recursion else 0.0) + min(line_count, 200) * 0.0009 ) return { "cyclomatic_complexity": cyclomatic_complexity, "time_complexity": _estimate_time_complexity(loop_depth, uses_recursion), "space_complexity": _estimate_space_complexity(code, uses_recursion), "complexity_penalty": round(complexity_penalty, 4), }