Spaces:
Running
Running
File size: 4,388 Bytes
28a08e7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | """
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
|