Spaces:
Sleeping
Sleeping
| /** | |
| * 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<AstCheckResult> { | |
| 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"); | |
| } | |