Terminal / tools /ast_check.py
Pulka
sync: 113 file da Baida98/AI@8b66af87 (2026-06-21 20:19 UTC)
517ba1c verified
Raw
History Blame Contribute Delete
4.39 kB
"""
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")
@dataclass
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