/** * astCheck.ts — P45: AST syntax check via TypeScript compiler API. * Zero shell, feedback istantaneo. Verifica sintassi PRIMA di eseguire/salvare. * Supporta .ts .tsx .js .jsx .mjs .cjs .mts .cts * Degradazione graceful se TypeScript non disponibile. * * @module astCheck */ import type * as TsType from "typescript"; export interface AstError { line: number; col: number; message: string; } export interface AstCheckResult { ok: boolean; errors: AstError[]; } const CODE_EXTS = new Set(["ts", "tsx", "js", "jsx", "mjs", "cjs", "mts", "cts"]); /** * Verifica sintattica AST di un frammento di codice tramite TypeScript compiler API. * Usa `parseDiagnostics` interno — stesso meccanismo degli IDE — senza shell né network. */ export async function astCheck(code: string, filename: string): Promise { const ext = (filename.split(".").pop() ?? "").toLowerCase(); if (!CODE_EXTS.has(ext)) return { ok: true, errors: [] }; // Skipa file estremamente piccoli (stub, placeholder) if (code.trim().length < 20) return { ok: true, errors: [] }; try { const ts = (await import("typescript")) as typeof TsType; const scriptKind = ext === "tsx" ? ts.ScriptKind.TSX : ext === "jsx" ? ts.ScriptKind.JSX : ext === "ts" || ext === "mts" || ext === "cts" ? ts.ScriptKind.TS : ts.ScriptKind.JS; // createSourceFile con setParentNodes=true popola parseDiagnostics const sf = ts.createSourceFile(filename, code, ts.ScriptTarget.Latest, true, scriptKind); // parseDiagnostics è il campo interno che gli IDE usano per evidenziare errori in real-time. // È stabile dalla TS 3.x ed è esattamente ciò di cui abbiamo bisogno (solo errori sintattici). type SfInternal = { parseDiagnostics?: TsType.Diagnostic[] }; const rawDiags: TsType.Diagnostic[] = (sf as unknown as SfInternal).parseDiagnostics ?? []; if (rawDiags.length === 0) return { ok: true, errors: [] }; const errors: AstError[] = rawDiags.map(d => { const pos = sf.getLineAndCharacterOfPosition(d.start ?? 0); const msg = typeof d.messageText === "string" ? d.messageText : (d.messageText as TsType.DiagnosticMessageChain).messageText; return { line: pos.line + 1, col: pos.character + 1, message: msg }; }); return { ok: false, errors }; } catch { // TypeScript non disponibile o errore interno → fail-open (non blocca il loop) return { ok: true, errors: [] }; } } /** * Costruisce il repair hint da iniettare come messaggio "user" nel loop. */ export function buildAstRepairHint(filename: string, errors: AstError[], code: string): string { const lines = code.split("\n"); const snippets = errors.slice(0, 3).map(e => { const lineContent = (lines[e.line - 1] ?? "").trim().slice(0, 120); return ` • riga ${e.line}, col ${e.col}: ${e.message}\n → ${lineContent}`; }); const extra = errors.length > 3 ? `\n (+ altri ${errors.length - 3} errori sintattici)` : ""; return [ `❌ ERRORE SINTATTICO AST in \`${filename}\` — ${errors.length} errore/i trovato/i:`, ...snippets, extra, ``, `AZIONE RICHIESTA: Riscrivi \`${filename}\` correggendo TUTTI gli errori sintattici sopra.`, `⚠️ Il file ha sintassi non valida — non può essere eseguito finché non corretto.`, `Usa write_file per riscrivere il file con la sintassi corretta, poi riprova.`, ].join("\n"); }