File size: 2,209 Bytes
019e7db
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
"""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),
    }