diff --git a/backend/app/services/ast_analyzer.py b/backend/app/services/ast_analyzer.py index a01602f..3955597 100644 --- a/backend/app/services/ast_analyzer.py +++ b/backend/app/services/ast_analyzer.py @@ -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 { @@ -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: @@ -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) diff --git a/backend/tests/test_ast_analyzer.py b/backend/tests/test_ast_analyzer.py index 20fa05f..d65a5a2 100644 --- a/backend/tests/test_ast_analyzer.py +++ b/backend/tests/test_ast_analyzer.py @@ -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 ───────────────────────────────────────────────── @@ -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