Standardize settings file naming and relocate documentation files Fix code quality violations from rsx:check Reorganize user_management directory into logical subdirectories Move Quill Bundle to core and align with Tom Select pattern Simplify Site Settings page to focus on core site information Complete Phase 5: Multi-tenant authentication with login flow and site selection Add route query parameter rule and synchronize filename validation logic Fix critical bug in UpdateNpmCommand causing missing JavaScript stubs Implement filename convention rule and resolve VS Code auto-rename conflict Implement js-sanitizer RPC server to eliminate 900+ Node.js process spawns Implement RPC server architecture for JavaScript parsing WIP: Add RPC server infrastructure for JS parsing (partial implementation) Update jqhtml terminology from destroy to stop, fix datagrid DOM preservation Add JQHTML-CLASS-01 rule and fix redundant class names Improve code quality rules and resolve violations Remove legacy fatal error format in favor of unified 'fatal' error type Filter internal keys from window.rsxapp output Update button styling and comprehensive form/modal documentation Add conditional fly-in animation for modals Fix non-deterministic bundle compilation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
155 lines
5.9 KiB
JavaScript
155 lines
5.9 KiB
JavaScript
// 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 <unknown>:${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 </${opening}>, found </${closing}>`, 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 <% endif; %>?';
|
|
}
|
|
if (error.includes('Unclosed for statement')) {
|
|
return '\nDid you forget <% endfor; %>?';
|
|
}
|
|
if (error.includes('Unclosed component definition')) {
|
|
return '\nDid you forget the closing </Define:ComponentName> 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 </div>)\n' +
|
|
' • Tag opened in if/for block but closed outside (split tag conditional - not allowed)\n' +
|
|
' • Mismatched nesting (e.g., <div><span></div></span>)\n\n' +
|
|
'If opened in <% if/for %>, it MUST close in the same block:\n' +
|
|
' ❌ <% if (x) { %> <div> <% } %> </div>\n' +
|
|
' ✅ <% if (x) { %> <div>...</div> <% } %>';
|
|
}
|
|
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
|