"""SecureCodeEnv - Documentation & Structure Graders v4 — clamped scores""" import ast from graders.clamp import clamp def grade_documentation(code: str) -> dict: try: tree = ast.parse(code) except SyntaxError: return {"score": clamp(0.0), "documented_fns": 0, "total_fns": 0, "feedback": "Syntax error — cannot parse"} functions = [n for n in ast.walk(tree) if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef))] if not functions: has_mod_doc = bool(ast.get_docstring(tree)) return {"score": clamp(0.65 if has_mod_doc else 0.5), "documented_fns": 0, "total_fns": 0, "feedback": "No functions — module-level code only"} scores = [] documented = typed = 0 for fn in functions: s = 0.0 hd = bool(ast.get_docstring(fn)) hr = fn.returns is not None hp = any(a.annotation is not None for a in fn.args.args) if hd: documented += 1; s += 0.5 if hr or hp: typed += 1; s += 0.5 scores.append(s) total = len(functions) raw = sum(scores) / total return {"score": clamp(raw), "documented_fns": documented, "typed_fns": typed, "total_fns": total, "feedback": _doc_feedback(raw, documented, typed, total)} def grade_code_structure(code: str) -> dict: try: tree = ast.parse(code) except SyntaxError: return {"score": clamp(0.0), "checks": {}, "feedback": "Syntax error"} lines = code.splitlines() checks = {} checks["no_bare_print"] = "print(" not in code checks["no_bare_except"] = not any( isinstance(n, ast.ExceptHandler) and n.type is None for n in ast.walk(tree)) checks["reasonable_fn_size"] = not any( isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef)) and (n.end_lineno or 0) - n.lineno > 50 for n in ast.walk(tree)) checks["no_todo_comments"] = not any( any(kw in line.upper() for kw in ["# TODO", "# FIXME", "# HACK"]) for line in lines) checks["handles_none"] = any( token in code for token in ["None", "is not None", "if not ", "Optional", "is None"]) raw = sum(1 for v in checks.values() if v) / max(len(checks), 1) return {"score": clamp(raw), "checks": checks, "feedback": _struct_feedback(raw, checks)} def _doc_feedback(score, documented, typed, total): if score >= 0.85: return f"Well documented — {documented}/{total} docstrings, {typed}/{total} typed" elif score >= 0.55: return f"Partial — {documented}/{total} docstrings, {typed}/{total} type hints" return f"Poor — add docstrings and type hints to all {total} functions" def _struct_feedback(score, checks): if score >= 0.85: return "Clean code structure" failing = [k for k, v in checks.items() if not v] return f"Structure issues: {', '.join(failing)}"