Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions backend/app/services/ast_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

_MUTABLE_TYPES = (ast.List, ast.Dict, ast.Set)

EMPTY_INPUT_MESSAGE = "No code provided. Analysis skipped."


def _issue(type_: str, description: str, suggestion: str, severity: str, line: int) -> dict:
return {
Expand Down Expand Up @@ -117,6 +119,9 @@ def analyze_python_ast(code: str) -> list[dict]:
Returns a list of issue dicts. If the code has a syntax error,
returns a single issue describing it instead of crashing.
"""
if code is None or not str(code).strip():
return [_issue("Empty Input", EMPTY_INPUT_MESSAGE, "Provide Python code to analyze.", "info", 0)]

try:
tree = ast.parse(code)
except SyntaxError as exc:
Expand Down Expand Up @@ -304,6 +309,8 @@ def walk(node, depth):
return issues

def analyze(source: str) -> list[dict]:
if source is None or not str(source).strip():
return [_issue("Empty Input", EMPTY_INPUT_MESSAGE, "Provide Python code to analyze.", "info", 0)]
tree = ast.parse(source)
issues = []
issues += detect_unreachable_code(tree, source)
Expand Down
49 changes: 47 additions & 2 deletions backend/tests/test_ast_analyzer.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
"""Tests for the AST-based Python analyzer."""

from app.services.ast_analyzer import analyze_python_ast, analyze
from app.services.ast_analyzer import analyze_python_ast, analyze, EMPTY_INPUT_MESSAGE


def _types(code: str) -> list[str]:
return [i["type"] for i in analyze_python_ast(code)]
result = analyze_python_ast(code)
if not isinstance(result, list):
return []
return [i["type"] for i in result]


# ── Mutable Default Arguments ─────────────────────────────────────────────────
Expand Down Expand Up @@ -230,3 +233,45 @@ def test_deep_nesting_exact_boundary():
)
issues = analyze(code)
assert not any(i["type"] == "Deep Nesting" for i in issues)


# ── Empty / Whitespace-Only Input ─────────────────────────────────────────────

def test_empty_input_returns_empty_list():
result = analyze_python_ast("")
assert len(result) == 1
assert result[0]["type"] == "Empty Input"
assert result[0]["description"] == EMPTY_INPUT_MESSAGE

def test_whitespace_only_input_returns_empty_list():
result = analyze_python_ast(" \n ")
assert len(result) == 1
assert result[0]["type"] == "Empty Input"
assert result[0]["description"] == EMPTY_INPUT_MESSAGE

def test_empty_input_via_analyze_function():
result = analyze("")
assert len(result) == 1
assert result[0]["type"] == "Empty Input"
assert result[0]["description"] == EMPTY_INPUT_MESSAGE

def test_none_input_returns_empty_list():
result = analyze_python_ast(None)
assert len(result) == 1
assert result[0]["type"] == "Empty Input"
assert result[0]["description"] == EMPTY_INPUT_MESSAGE

def test_none_input_via_analyze_function():
result = analyze(None)
assert len(result) == 1
assert result[0]["type"] == "Empty Input"
assert result[0]["description"] == EMPTY_INPUT_MESSAGE


def test_empty_input_has_standard_keys():
for code in ["", " ", None]:
result = analyze_python_ast(code) if code is not None else analyze_python_ast(None)
assert isinstance(result, list)
assert len(result) == 1
assert result[0]["type"] == "Empty Input"
assert result[0]["description"] == EMPTY_INPUT_MESSAGE