| """ |
| Utility helpers for the Code Review NLP Assistant. |
| """ |
|
|
| from __future__ import annotations |
| import ast |
| import re |
| from typing import Any |
|
|
|
|
| |
|
|
| def extract_functions(code: str) -> list[dict[str, Any]]: |
| """ |
| Parse Python source and return metadata for each function/method. |
| Returns a list of dicts with keys: |
| name, args, returns, has_docstring, lineno, end_lineno, source |
| """ |
| results = [] |
| try: |
| tree = ast.parse(code) |
| except SyntaxError: |
| return results |
|
|
| lines = code.splitlines() |
|
|
| for node in ast.walk(tree): |
| if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): |
| continue |
|
|
| |
| args = [a.arg for a in node.args.args] |
|
|
| |
| returns = "" |
| if node.returns: |
| try: |
| returns = ast.unparse(node.returns) |
| except Exception: |
| returns = "?" |
|
|
| |
| has_doc = ( |
| isinstance(node.body[0], ast.Expr) |
| and isinstance(node.body[0].value, ast.Constant) |
| and isinstance(node.body[0].value.value, str) |
| ) if node.body else False |
|
|
| end = getattr(node, "end_lineno", node.lineno) |
| src_lines = lines[node.lineno - 1 : end] |
| source = "\n".join(src_lines) |
|
|
| results.append({ |
| "name": node.name, |
| "args": args, |
| "returns": returns, |
| "has_docstring": has_doc, |
| "lineno": node.lineno, |
| "end_lineno": end, |
| "source": source, |
| }) |
|
|
| return results |
|
|
|
|
| def extract_classes(code: str) -> list[dict[str, Any]]: |
| """Return a list of class metadata dicts.""" |
| results = [] |
| try: |
| tree = ast.parse(code) |
| except SyntaxError: |
| return results |
|
|
| for node in ast.walk(tree): |
| if not isinstance(node, ast.ClassDef): |
| continue |
| methods = [ |
| n.name for n in ast.walk(node) |
| if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef)) |
| ] |
| has_doc = ( |
| isinstance(node.body[0], ast.Expr) |
| and isinstance(node.body[0].value, ast.Constant) |
| ) if node.body else False |
|
|
| results.append({ |
| "name": node.name, |
| "methods": methods, |
| "has_docstring": has_doc, |
| "lineno": node.lineno, |
| }) |
|
|
| return results |
|
|
|
|
| def detect_language(code: str) -> str: |
| """Very simple language heuristic.""" |
| if re.search(r'\bdef\b.*:\s*$', code, re.MULTILINE): |
| return "python" |
| if re.search(r'\bfunction\b|\bconst\b|\blet\b|\bvar\b', code): |
| return "javascript" |
| if re.search(r'\bpublic\b.*\bclass\b', code): |
| return "java" |
| return "unknown" |
|
|
|
|
| |
|
|
| GRADE_MAP = [ |
| (90, "A", "Excellent"), |
| (75, "B", "Good"), |
| (60, "C", "Needs work"), |
| (40, "D", "Poor"), |
| (0, "F", "Critical"), |
| ] |
|
|
|
|
| def score_to_grade(score: float) -> tuple[str, str]: |
| """Return (letter_grade, label) for a 0-100 score.""" |
| for threshold, letter, label in GRADE_MAP: |
| if score >= threshold: |
| return letter, label |
| return "F", "Critical" |
|
|
|
|
| def score_color(score: float) -> str: |
| """Return a hex colour representing the score quality.""" |
| if score >= 80: return "#22c55e" |
| if score >= 60: return "#f59e0b" |
| if score >= 40: return "#f97316" |
| return "#ef4444" |
|
|
|
|
| def build_report(result: Any, filename: str = "code_review") -> str: |
| """Generate a Markdown report from a CodeQualityResult.""" |
| grade, label = score_to_grade(result.overall_score) |
| lines = [ |
| f"# Code Review Report β `{filename}`\n", |
| f"## Overall Score: {result.overall_score}/100 ({grade} β {label})\n", |
| "### Sub-scores\n", |
| "| Metric | Score |", |
| "|--------|-------|", |
| f"| Documentation | {result.documentation_score}/100 |", |
| f"| Naming Quality | {result.naming_score}/100 |", |
| f"| Complexity | {result.complexity_score}/100 |", |
| "", |
| ] |
|
|
| if result.issues: |
| lines.append("### Issues Found\n") |
| for issue in result.issues: |
| lines.append(f"- {issue}") |
| lines.append("") |
|
|
| if result.suggestions: |
| lines.append("### Suggestions\n") |
| for s in result.suggestions: |
| lines.append(f"- {s}") |
| lines.append("") |
|
|
| if result.generated_docstring: |
| lines.append("### Generated Docstring (CodeT5)\n") |
| lines.append("```python") |
| lines.append(result.generated_docstring) |
| lines.append("```\n") |
|
|
| lines.append("---") |
| lines.append("*Generated by Code Review NLP Assistant using CodeBERT + CodeT5*") |
|
|
| return "\n".join(lines) |