// JQHTML Parser Error Classes // Provides helpful error messages with code context export class JQHTMLParseError extends Error { line; column; endLine; endColumn; source; filename; severity = 'error'; suggestion; constructor(message, line, column, source, filename) { super(message); this.name = 'JQHTMLParseError'; this.line = line; this.column = column; this.source = source; this.filename = filename; // Auto-add suggestions this.suggestion = getSuggestion(message); // Build the full error message with context this.message = this.buildErrorMessage(message); } buildErrorMessage(message) { let result = message; // Add location info IMMEDIATELY after message (required for RSpade parsing) // Format: "at filename:line:column" (RSpade regex: /at [^:]+:(\d+):(\d+)/i) if (this.filename) { result += `\nat ${this.filename}:${this.line}:${this.column}`; } else { // Fallback to generic format if no filename result += `\nat :${this.line}:${this.column}`; } // Add code snippet if source is available if (this.source) { const snippet = this.getCodeSnippet(); if (snippet) { result += '\n\n' + snippet; } } // Add suggestion AFTER code snippet (don't interfere with parsing) if (this.suggestion) { result += this.suggestion; } return result; } getCodeSnippet() { if (!this.source) return ''; const lines = this.source.split('\n'); const lineIndex = this.line - 1; // Show 3 lines before and after for better context const contextLines = 3; const startLine = Math.max(0, lineIndex - contextLines); const endLine = Math.min(lines.length - 1, lineIndex + contextLines); let snippet = ''; for (let i = startLine; i <= endLine; i++) { const lineNum = i + 1; const isErrorLine = i === lineIndex; const prefix = isErrorLine ? '>' : ' '; // Line number with padding const lineNumStr = String(lineNum).padStart(5, ' '); snippet += `${prefix} ${lineNumStr} | ${lines[i]}\n`; // Add pointer to error column with better highlighting if (isErrorLine) { const spaces = ' '.repeat(this.column + 8); const carets = '^'.repeat(Math.min(lines[i].length - this.column + 1, 20)); snippet += `${spaces}${carets}\n`; } } return snippet; } } // Common error factories export function unclosedError(type, name, line, column, source, filename) { return new JQHTMLParseError(`Unclosed ${type}: ${name}`, line, column, source, filename); } export function mismatchedTagError(opening, closing, line, column, source, filename) { return new JQHTMLParseError(`Mismatched tags: expected , found `, line, column, source, filename); } export function syntaxError(message, line, column, source, filename) { return new JQHTMLParseError(`Syntax error: ${message}`, line, column, source, filename); } // Helpful suggestions for common mistakes export function getSuggestion(error) { if (error.includes('Unclosed if statement')) { return '\nDid you forget the closing <% } %>?'; } if (error.includes('Unclosed for statement')) { return '\nDid you forget the closing <% } %>?'; } if (error.includes('Unclosed component definition')) { return '\nDid you forget the closing tag?'; } if (error.includes('Unclosed slot')) { return '\nDid you mean to use a self-closing slot? Try <#name /> instead.'; } if (error.includes('Unclosed tag') || error.includes('Unclosed component')) { return '\n\nThis element was opened but never closed. Make sure every opening tag has a matching closing tag.\n' + 'Common causes:\n' + ' • Missing closing tag (e.g., forgot )\n' + ' • Tag opened in if/for block but closed outside (split tag conditional - not allowed)\n' + ' • Mismatched nesting (e.g.,
)\n\n' + 'If opened in <% if/for %>, it MUST close in the same block:\n' + ' ❌ <% if (x) { %>
<% } %>
\n' + ' ✅ <% if (x) { %>
...
<% } %>'; } if (error.includes('Expected %>')) { return '\nCheck if you have a %> inside a string literal. Escape it or use a different approach.'; } if (error.includes('Mismatched tags')) { return '\nCheck that your opening and closing tags match exactly (case-sensitive).'; } if (error.includes('Mixed content not allowed')) { return '\nWhen using slots, wrap all content in <#slotname> tags. Use <#default> for the main content.'; } return ''; } // Error collection for batch reporting export class ErrorCollector { errors = []; maxErrors = 10; constructor(maxErrors = 10) { this.maxErrors = maxErrors; } add(error) { if (this.errors.length < this.maxErrors) { this.errors.push(error); } } hasErrors() { return this.errors.length > 0; } getErrors() { return this.errors; } throwIfErrors() { if (this.errors.length === 0) return; if (this.errors.length === 1) { throw this.errors[0]; } // Multiple errors - create a combined message let message = `Found ${this.errors.length} errors:\n\n`; this.errors.forEach((error, index) => { message += `Error ${index + 1}: ${error.message}\n`; if (index < this.errors.length - 1) { message += '\n'; } }); throw new Error(message); } } //# sourceMappingURL=errors.js.map