Spaces:
Runtime error
Runtime error
| """Static parsing helpers for multi-domain Python code analysis.""" | |
| from __future__ import annotations | |
| import ast | |
| from typing import Any, Dict, List | |
| class _LoopDepthVisitor(ast.NodeVisitor): | |
| """Collect loop nesting depth for a parsed Python module.""" | |
| def __init__(self) -> None: | |
| self.depth = 0 | |
| self.max_depth = 0 | |
| def _visit_loop(self, node: ast.AST) -> None: | |
| self.depth += 1 | |
| self.max_depth = max(self.max_depth, self.depth) | |
| self.generic_visit(node) | |
| self.depth -= 1 | |
| def visit_For(self, node: ast.For) -> None: # noqa: N802 | |
| self._visit_loop(node) | |
| def visit_While(self, node: ast.While) -> None: # noqa: N802 | |
| self._visit_loop(node) | |
| def visit_comprehension(self, node: ast.comprehension) -> None: # noqa: N802 | |
| self._visit_loop(node) | |
| def parse_code_structure(code: str) -> Dict[str, Any]: | |
| """Parse Python code into reusable structural signals.""" | |
| summary: Dict[str, Any] = { | |
| "syntax_valid": True, | |
| "syntax_error": "", | |
| "imports": [], | |
| "function_names": [], | |
| "class_names": [], | |
| "loop_count": 0, | |
| "branch_count": 0, | |
| "max_loop_depth": 0, | |
| "line_count": len(code.splitlines()), | |
| "long_lines": 0, | |
| "tabs_used": "\t" in code, | |
| "trailing_whitespace_lines": 0, | |
| "uses_numpy": False, | |
| "uses_pandas": False, | |
| "uses_torch": False, | |
| "uses_sklearn": False, | |
| "uses_fastapi": False, | |
| "uses_flask": False, | |
| "uses_pydantic": False, | |
| "uses_recursion": False, | |
| "calls_eval": False, | |
| "calls_no_grad": False, | |
| "calls_backward": False, | |
| "calls_optimizer_step": False, | |
| "route_decorators": [], | |
| "docstring_ratio": 0.0, | |
| "code_smells": [], | |
| } | |
| lines = code.splitlines() | |
| summary["long_lines"] = sum(1 for line in lines if len(line) > 88) | |
| summary["trailing_whitespace_lines"] = sum(1 for line in lines if line.rstrip() != line) | |
| try: | |
| tree = ast.parse(code) | |
| except SyntaxError as exc: | |
| summary["syntax_valid"] = False | |
| summary["syntax_error"] = f"{exc.msg} (line {exc.lineno})" | |
| summary["code_smells"].append("Code does not parse.") | |
| return summary | |
| visitor = _LoopDepthVisitor() | |
| visitor.visit(tree) | |
| summary["max_loop_depth"] = visitor.max_depth | |
| functions = [node for node in tree.body if isinstance(node, ast.FunctionDef)] | |
| summary["function_names"] = [node.name for node in functions] | |
| summary["class_names"] = [node.name for node in tree.body if isinstance(node, ast.ClassDef)] | |
| summary["docstring_ratio"] = ( | |
| sum(1 for node in functions if ast.get_docstring(node)) / len(functions) | |
| if functions | |
| else 0.0 | |
| ) | |
| imports: List[str] = [] | |
| for node in ast.walk(tree): | |
| if isinstance(node, ast.Import): | |
| imports.extend(alias.name.split(".")[0] for alias in node.names) | |
| elif isinstance(node, ast.ImportFrom) and node.module: | |
| imports.append(node.module.split(".")[0]) | |
| elif isinstance(node, (ast.For, ast.While, ast.comprehension)): | |
| summary["loop_count"] += 1 | |
| elif isinstance(node, (ast.If, ast.Try, ast.Match)): | |
| summary["branch_count"] += 1 | |
| elif isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute): | |
| attr = node.func.attr | |
| if attr == "eval": | |
| summary["calls_eval"] = True | |
| elif attr == "backward": | |
| summary["calls_backward"] = True | |
| elif attr == "step": | |
| summary["calls_optimizer_step"] = True | |
| elif isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == "print": | |
| summary["code_smells"].append("Debug print statements are present.") | |
| elif isinstance(node, ast.With): | |
| if any(isinstance(item.context_expr, ast.Call) and isinstance(item.context_expr.func, ast.Attribute) and item.context_expr.func.attr == "no_grad" for item in node.items): | |
| summary["calls_no_grad"] = True | |
| import_set = sorted(set(imports)) | |
| summary["imports"] = import_set | |
| summary["uses_numpy"] = "numpy" in import_set or "np" in code | |
| summary["uses_pandas"] = "pandas" in import_set or "pd" in code | |
| summary["uses_torch"] = "torch" in import_set | |
| summary["uses_sklearn"] = "sklearn" in import_set | |
| summary["uses_fastapi"] = "fastapi" in import_set | |
| summary["uses_flask"] = "flask" in import_set | |
| summary["uses_pydantic"] = "pydantic" in import_set or "BaseModel" in code | |
| for node in functions: | |
| for child in ast.walk(node): | |
| if isinstance(child, ast.Call) and isinstance(child.func, ast.Name) and child.func.id == node.name: | |
| summary["uses_recursion"] = True | |
| for node in ast.walk(tree): | |
| if isinstance(node, ast.FunctionDef): | |
| for decorator in node.decorator_list: | |
| if isinstance(decorator, ast.Call) and isinstance(decorator.func, ast.Attribute): | |
| summary["route_decorators"].append(decorator.func.attr) | |
| elif isinstance(decorator, ast.Attribute): | |
| summary["route_decorators"].append(decorator.attr) | |
| if summary["long_lines"]: | |
| summary["code_smells"].append("Long lines reduce readability.") | |
| if summary["tabs_used"]: | |
| summary["code_smells"].append("Tabs detected; prefer spaces for consistency.") | |
| if summary["trailing_whitespace_lines"]: | |
| summary["code_smells"].append("Trailing whitespace found.") | |
| return summary | |