Spaces:
Sleeping
Sleeping
| """ | |
| ast_check.py — P45: AST syntax check pre-esecuzione codice. | |
| Usa ast.parse() stdlib Python (zero dipendenze) per Python. | |
| Per JS/TS: esprima se disponibile, fallback balanced-bracket heuristic. | |
| Integra P17-B1: sostituisce routing regex E2B per validazione sintattica. | |
| Utilizzo: | |
| from tools.ast_check import check_code_syntax | |
| result = check_code_syntax(code, "python") | |
| if not result.ok: | |
| return f"SyntaxError: {result.error}" | |
| """ | |
| import ast | |
| import re | |
| import logging | |
| from dataclasses import dataclass | |
| _logger = logging.getLogger("tools.ast_check") | |
| class SyntaxCheckResult: | |
| ok: bool | |
| error: str | None = None | |
| language: str = "" | |
| line: int | None = None | |
| col: int | None = None | |
| def __str__(self): | |
| if self.ok: | |
| return f"[AST] {self.language}: OK" | |
| loc = f" (line {self.line})" if self.line else "" | |
| return f"[AST] {self.language}{loc}: {self.error}" | |
| def check_python_syntax(code): | |
| """AST check Python con ast.parse() stdlib. Zero dipendenze.""" | |
| try: | |
| ast.parse(code, mode="exec") | |
| return SyntaxCheckResult(ok=True, language="python") | |
| except SyntaxError as e: | |
| return SyntaxCheckResult( | |
| ok=False, language="python", | |
| error=f"{type(e).__name__}: {e.msg}", | |
| line=e.lineno, col=e.offset, | |
| ) | |
| except Exception as e: | |
| return SyntaxCheckResult(ok=False, language="python", error=str(e)) | |
| def _balanced_brackets(code): | |
| """Heuristica bilanciamento parentesi — fallback JS senza esprima.""" | |
| stack = [] | |
| pairs = {")": "(", "}": "{", "]": "["} | |
| in_str = None | |
| i = 0 | |
| while i < len(code): | |
| ch = code[i] | |
| if in_str: | |
| if ch == "\\" and i + 1 < len(code): | |
| i += 2 | |
| continue | |
| if ch == in_str: | |
| in_str = None | |
| elif ch in ('"', "'", '`'): | |
| in_str = ch | |
| elif ch in "({[": | |
| stack.append(ch) | |
| elif ch in ")}]": | |
| if not stack or stack[-1] != pairs[ch]: | |
| return SyntaxCheckResult(ok=False, language="js/ts", error=f"Unmatched '{ch}' at pos {i}") | |
| stack.pop() | |
| i += 1 | |
| if stack: | |
| return SyntaxCheckResult(ok=False, language="js/ts", error=f"Unclosed '{stack[-1]}'") | |
| return SyntaxCheckResult(ok=True, language="js/ts") | |
| def check_js_syntax(code): | |
| """AST check JS/TS: esprima se disponibile, fallback balanced-bracket.""" | |
| try: | |
| import esprima | |
| esprima.parseScript(code, tolerant=False) | |
| return SyntaxCheckResult(ok=True, language="js/ts") | |
| except ImportError: | |
| pass | |
| except Exception as e: | |
| msg = str(e) | |
| m = re.search(r"Line (\d+)", msg) | |
| line = int(m.group(1)) if m else None | |
| return SyntaxCheckResult(ok=False, language="js/ts", error=msg[:200], line=line) | |
| return _balanced_brackets(code) | |
| _LANG_MAP = { | |
| "python": "python", "py": "python", | |
| "javascript": "js", "js": "js", "jsx": "js", | |
| "typescript": "js", "ts": "js", "tsx": "js", | |
| } | |
| def check_code_syntax(code, language): | |
| """ | |
| Dispatch AST check per linguaggio. Fail-open su linguaggi non supportati. | |
| Returns SyntaxCheckResult(ok, error, language, line, col). | |
| """ | |
| if not code or not code.strip(): | |
| return SyntaxCheckResult(ok=True, language=language) | |
| lang = _LANG_MAP.get(language.lower().strip(), "") | |
| if lang == "python": | |
| result = check_python_syntax(code) | |
| elif lang == "js": | |
| result = check_js_syntax(code) | |
| else: | |
| return SyntaxCheckResult(ok=True, language=language) | |
| if not result.ok: | |
| _logger.warning("[AST-P45] %s syntax error: %s", language, result.error) | |
| return result | |
| def extract_code_blocks(text): | |
| """Estrae blocchi codice da markdown. Ritorna list[(language, code)].""" | |
| blocks = [] | |
| pattern = re.compile(r'```(\w+)?\n([\s\S]*?)```') | |
| for m in pattern.finditer(text): | |
| lang = (m.group(1) or "").strip() | |
| code = m.group(2) | |
| blocks.append((lang, code)) | |
| return blocks | |
| def check_all_code_blocks(text): | |
| """Verifica tutti i blocchi codice in markdown. Ritorna solo errori.""" | |
| errors = [] | |
| for lang, code in extract_code_blocks(text): | |
| result = check_code_syntax(code, lang) | |
| if not result.ok: | |
| errors.append(result) | |
| return errors | |