UNPKG

@ordojs/core

Version:

Core compiler and runtime for OrdoJS framework

324 lines 12.9 kB
/** * @fileoverview OrdoJS Parser Fixes - Improvements for type safety and error handling */ import { DirectiveType, ExpressionType, SyntaxError, TokenType } from '../types/index.js'; /** * Improved parser error handling and type safety */ export class ParserErrorHandler { /** * Create a properly typed syntax error with improved suggestions */ static createError(message, expected, actual, position, filename) { // Enhance error message with context let enhancedMessage = message; // Add specific suggestions based on error context const suggestions = ParserErrorHandler.generateSuggestions(expected, actual, message); return new SyntaxError(enhancedMessage, position, expected, actual, filename); } /** * Generate helpful suggestions based on error context */ static generateSuggestions(expected, actual, message) { const suggestions = []; // Component structure suggestions if (message.includes("component")) { suggestions.push("Make sure to start with 'component ComponentName {'"); suggestions.push("Component must have at least a markup block"); } // Block-specific suggestions if (message.includes("client") || message.includes("server") || message.includes("markup")) { suggestions.push("Each block type (client, server, markup) can only appear once"); suggestions.push("Blocks must be properly closed with '}'"); } // HTML-specific suggestions if (message.includes("tag")) { suggestions.push("Check for matching opening and closing tags"); suggestions.push("Self-closing tags should end with '/>'"); } // Expression suggestions if (message.includes("expression")) { suggestions.push("Check for balanced parentheses and operators"); suggestions.push("Verify variable names and property access syntax"); } return suggestions; } /** * Validate component structure */ static validateComponentStructure(component) { const errors = []; // Validate required markup block if (!component.markupBlock) { errors.push(new SyntaxError("Component must have a markup block", component.range.start, ["markup"], "missing", "")); } // Validate component name (must start with uppercase letter) if (!/^[A-Z][a-zA-Z0-9]*$/.test(component.name)) { errors.push(new SyntaxError("Component name must start with uppercase letter and contain only alphanumeric characters", component.range.start, ["ValidComponentName"], component.name)); } // Validate no duplicate blocks const blockCounts = { client: 0, server: 0, markup: 0 }; component.children?.forEach(child => { if (child.type === 'ClientBlock') blockCounts.client++; if (child.type === 'ServerBlock') blockCounts.server++; if (child.type === 'MarkupBlock') blockCounts.markup++; }); if (blockCounts.client > 1) { errors.push(new SyntaxError("Multiple client blocks found", component.range.start, ["single client block"], `${blockCounts.client} client blocks`)); } if (blockCounts.server > 1) { errors.push(new SyntaxError("Multiple server blocks found", component.range.start, ["single server block"], `${blockCounts.server} server blocks`)); } if (blockCounts.markup > 1) { errors.push(new SyntaxError("Multiple markup blocks found", component.range.start, ["single markup block"], `${blockCounts.markup} markup blocks`)); } return errors; } /** * Validate HTML structure */ static validateHTMLStructure(element) { const errors = []; // Check for void elements with children if (element.isVoidElement && element.children.length > 0) { errors.push(new SyntaxError(`Void element <${element.tagName}> cannot have children`, element.range.start, ["self-closing tag"], "tag with children")); } // Check for duplicate ID attributes const idAttributes = element.attributes.filter(attr => attr.name === 'id'); if (idAttributes.length > 1) { errors.push(new SyntaxError("Element cannot have multiple 'id' attributes", element.range.start, ["single id attribute"], `${idAttributes.length} id attributes`)); } // Recursively validate children element.children.forEach(child => { if (child.type === 'HTMLElement') { errors.push(...ParserErrorHandler.validateHTMLStructure(child)); } }); return errors; } } /** * Null/undefined safety utilities for parser */ export class ParserSafetyUtils { /** * Safely access token value with null checking */ static safeTokenValue(token) { return token?.value || ''; } /** * Safely create a source range with null checking */ static safeCreateRange(start, end) { const defaultPos = { line: 0, column: 0, offset: 0 }; return { start: start || defaultPos, end: end || defaultPos }; } /** * Safely handle optional children */ static safeChildren(nodes) { return nodes.filter((node) => node !== null && node !== undefined); } /** * Safely handle optional expressions */ static safeExpression(expr) { if (expr) return expr; // Create a safe default expression const defaultPos = { line: 0, column: 0, offset: 0 }; const defaultRange = { start: defaultPos, end: defaultPos }; return { type: 'Expression', expressionType: ExpressionType.LITERAL, value: null, range: defaultRange }; } /** * Safely handle optional type annotations */ static safeTypeAnnotation(type) { return type || { name: 'any', isArray: false, isOptional: false, genericTypes: [] }; } } /** * Enhanced error recovery for parser */ export class ParserRecovery { /** * Synchronize parser state after error * @param tokens Token stream * @param synchronizationPoints Token types to synchronize on */ static synchronize(tokens, synchronizationPoints = [ TokenType.SEMICOLON, TokenType.RIGHT_BRACE, TokenType.RIGHT_PAREN, TokenType.HTML_TAG_CLOSE ]) { // Skip tokens until we reach a synchronization point while (!tokens.isAtEnd()) { if (synchronizationPoints.includes(tokens.peek().type)) { tokens.advance(); // Consume the synchronization token return; } // Skip to next token tokens.advance(); } } /** * Attempt to recover from HTML parsing errors */ static recoverFromHTMLError(tokens, tagName) { // Skip until we find a matching closing tag or another opening tag while (!tokens.isAtEnd()) { const current = tokens.peek(); if (current.type === TokenType.HTML_TAG_OPEN) { // Found another opening tag, stop here return; } if (current.type === TokenType.HTML_TAG_CLOSE) { // Found a closing tag, consume it and return tokens.advance(); return; } // Skip to next token tokens.advance(); } } /** * Attempt to recover from block parsing errors */ static recoverFromBlockError(tokens) { let braceCount = 0; // Skip until we find a matching closing brace while (!tokens.isAtEnd()) { const current = tokens.peek(); if (current.type === TokenType.LEFT_BRACE) { braceCount++; } else if (current.type === TokenType.RIGHT_BRACE) { braceCount--; if (braceCount <= 0) { tokens.advance(); // Consume the closing brace return; } } // Skip to next token tokens.advance(); } } } /** * Enhanced validation for parser */ export class ParserValidation { /** * Validate HTML element structure */ static validateHTMLElement(element) { const errors = []; // Check for void elements with closing tags const voidElements = [ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr' ]; if (voidElements.includes(element.tagName.toLowerCase()) && !element.isSelfClosing) { errors.push(new SyntaxError(`Void element <${element.tagName}> should be self-closing`, element.range.start, ["self-closing tag"], "non-self-closing tag")); } // Check for invalid nesting const invalidNesting = { 'a': ['a'], 'button': ['button', 'input', 'select', 'textarea', 'a'], 'form': ['form'], 'label': ['label'], 'td': ['td', 'th', 'tr', 'thead', 'tfoot', 'tbody'], 'th': ['td', 'th', 'tr', 'thead', 'tfoot', 'tbody'] }; const tagName = element.tagName.toLowerCase(); if (invalidNesting[tagName]) { for (const child of element.children) { if (child.type === 'HTMLElement') { const childTag = child.tagName.toLowerCase(); if (invalidNesting[tagName].includes(childTag)) { errors.push(new SyntaxError(`Invalid nesting: <${childTag}> cannot be nested inside <${tagName}>`, child.range.start, ["valid nesting"], `<${tagName}> containing <${childTag}>`)); } } } } // Recursively validate children for (const child of element.children) { if (child.type === 'HTMLElement') { errors.push(...ParserValidation.validateHTMLElement(child)); } } return errors; } /** * Validate directive usage */ static validateDirectives(element) { const errors = []; // Check for bind:value on non-input elements const bindValueAttrs = element.attributes.filter(attr => attr.isDirective && attr.directiveType === DirectiveType.BIND && attr.name === 'bind:value'); if (bindValueAttrs.length > 0 && !['input', 'textarea', 'select'].includes(element.tagName.toLowerCase())) { errors.push(new SyntaxError(`bind:value can only be used on input, textarea, or select elements`, element.range.start, ["input", "textarea", "select"], element.tagName)); } // Check for duplicate event handlers const eventHandlers = element.attributes.filter(attr => attr.isDirective && attr.directiveType === DirectiveType.ON); const eventNames = new Set(); for (const handler of eventHandlers) { const eventName = handler.name.split(':')[1] || ''; if (eventName && eventNames.has(eventName)) { errors.push(new SyntaxError(`Duplicate event handler for '${eventName}'`, handler.range.start, ["single event handler"], "multiple handlers")); } if (eventName) { eventNames.add(eventName); } } // Recursively validate children for (const child of element.children) { if (child.type === 'HTMLElement') { errors.push(...ParserValidation.validateDirectives(child)); } } return errors; } /** * Validate reactive variable declarations */ static validateReactiveVariables(variables) { const errors = []; const declaredNames = new Set(); for (const variable of variables) { // Check for duplicate declarations if (declaredNames.has(variable.name)) { errors.push(new SyntaxError(`Duplicate reactive variable declaration: '${variable.name}'`, variable.range.start, ["unique variable name"], variable.name)); } declaredNames.add(variable.name); // Check for const variables without initialization if (variable.isConst && !variable.initialValue) { errors.push(new SyntaxError(`Const variable '${variable.name}' must be initialized`, variable.range.start, ["initialization"], "missing initialization")); } } return errors; } } //# sourceMappingURL=parser-fixes.js.map