UNPKG

parsergen-starter

Version:

A complete parser generator starter with PEG.js, optional Moo lexer, and VS Code integration

362 lines (298 loc) 13.2 kB
import type { Location } from './types'; import type { ParseError } from '../parser/index'; // Consider if this should be from a dedicated types file like '../parser/types' import { highlightSnippet } from './highlight'; import * as colors from 'colorette'; // Correctly imported colorette as 'colors' // Type guard to safely check if an error is a ParseError export function isParseError(err: unknown): err is ParseError { return ( typeof err === 'object' && err !== null && 'error' in err && // Check if 'error' property exists typeof (err as Record<string, unknown>).error === 'string' && // Now check its type 'success' in err && typeof (err as Record<string, unknown>).success === 'boolean' // Add other essential ParseError properties if they must exist for it to be a ParseError ); } // Type guard to check if an error is a Peggy-style error export function isPeggyError(err: unknown): boolean { return ( typeof err === 'object' && err !== null && 'message' in err && typeof (err as Record<string, unknown>).message === 'string' && ('location' in err || 'expected' in err || 'found' in err) ); } // Safe wrapper for unknown errors with enhanced Peggy support export function toParseError(err: unknown): ParseError { // If already a ParseError (type guard) if (isParseError(err)) { return err; } // Peggy-style error if (isPeggyError(err)) { const peggyError = err as Record<string, unknown>; return { error: peggyError.message as string, location: isValidLocation(peggyError.location) ? (peggyError.location as Location) : undefined, success: false, expected: Array.isArray(peggyError.expected) ? (peggyError.expected as string[]) : undefined, found: typeof peggyError.found === 'string' ? (peggyError.found as string) : undefined, input: typeof peggyError.input === 'string' ? (peggyError.input as string) : undefined, snippet: undefined }; } // Standard JS Error if (err instanceof Error) { return { error: err.message, location: undefined, success: false, expected: undefined, found: undefined, input: undefined, snippet: undefined }; } // Fallback: unknown or malformed return { error: typeof err === 'string' ? err : 'Unknown error', location: undefined, success: false, expected: undefined, found: undefined, input: undefined, snippet: undefined }; } // CORRECTED isValidLocation FUNCTION function isValidLocation(loc: unknown): loc is Location { if (typeof loc !== 'object' || loc === null) { return false; } const locationObject = loc as Record<string, unknown>; // Assert to a record for property access // Check for 'start' and 'end' properties if (!('start' in locationObject) || !('end' in locationObject)) { return false; } const start = locationObject.start; const end = locationObject.end; // Check if 'start' is a non-null object if (typeof start !== 'object' || start === null) { return false; } const startObject = start as Record<string, unknown>; // Assert start to a record // Check start properties if ( !('line' in startObject) || typeof startObject.line !== 'number' || !('column' in startObject) || typeof startObject.column !== 'number' || !('offset' in startObject) || typeof startObject.offset !== 'number' ) { return false; } // Check if 'end' is a non-null object if (typeof end !== 'object' || end === null) { return false; } const endObject = end as Record<string, unknown>; // Assert end to a record // Check end properties if ( !('line' in endObject) || typeof endObject.line !== 'number' || !('column' in endObject) || typeof endObject.column !== 'number' || !('offset' in endObject) || typeof endObject.offset !== 'number' ) { return false; } return true; // All checks passed } // END CORRECTED isValidLocation FUNCTION export function formatLocation(location: Location): string { const { start, end } = location; return (start.line === end.line && start.column === end.column) ? `Line ${start.line}, Col ${start.column}` : `Line ${start.line}, Col ${start.column} → Line ${end.line}, Col ${end.column}`; } export function formatError(error: ParseError): string { const errorMessage = error.error || 'Unknown error'; const parts: string[] = [`❌ Parse Error: ${errorMessage}`]; if (error.location) { parts.push(`↪ at ${formatLocation(error.location)}`); } if (error.expected && error.expected.length > 0) { parts.push(`Expected: ${error.expected.join(', ')}`); } if (error.found !== undefined) { parts.push(`Found: "${error.found}"`); } if (error.snippet || (error.input && error.location)) { try { // FIX: Pass false for useColors to highlightSnippet in the non-colored formatError const snippet = error.snippet || highlightSnippet(error.input!, error.location!, false); parts.push('\n--- Snippet ---\n' + snippet); } catch { parts.push('\n--- Snippet unavailable ---'); } } return parts.join('\n'); } export function formatErrorWithColors(error: ParseError, useColors: boolean = true): string { if (!useColors) { return formatError(error); } const errorMessage = error.error || 'Unknown error'; const parts: string[] = [ `${colors.red('❌ Parse Error:')} ${errorMessage}` // FIX: Use colors.red ]; if (error.location) { parts.push(`${colors.blue('↪ at')} ${formatLocation(error.location)}`); // FIX: Use colors.blue } if (error.expected && error.expected.length > 0) { parts.push(`${colors.yellow('Expected:')} ${error.expected.join(', ')}`); // FIX: Use colors.yellow } if (error.found !== undefined) { parts.push(`${colors.yellow('Found:')} "${error.found}"`); // FIX: Use colors.yellow } if (error.snippet || (error.input && error.location)) { try { const snippet = error.snippet || highlightSnippet(error.input!, error.location!, useColors); parts.push('\n' + colors.dim('--- Snippet ---') + '\n' + snippet); // FIX: Use colors.dim } catch { parts.push('\n' + colors.dim('--- Snippet unavailable ---')); // FIX: Use colors.dim } } return parts.join('\n'); } export function formatSuccessMessage(message: string): string { return colors.green(`✅ ${message}`); // FIX: Use colors.green } export function formatWarningMessage(message: string): string { return colors.yellow(`⚠️ ${message}`); // FIX: Use colors.yellow } export function formatInfoMessage(message: string): string { return colors.blue(`ℹ️ ${message}`); // FIX: Use colors.blue } export function formatMultipleErrors(errors: ParseError[], useColors: boolean = true): string { if (!errors || errors.length === 0) return ''; const header = useColors ? colors.red(colors.bold(`Found ${errors.length} error${errors.length > 1 ? 's' : ''}:`)) // FIX: Nest colors.red and colors.bold : `Found ${errors.length} error${errors.length > 1 ? 's' : ''}:`; const formattedErrors = errors.map((error, index) => { const errorNum = useColors ? colors.dim(`[${index + 1}/${errors.length}]`) // FIX: Use colors.dim : `[${index + 1}/${errors.length}]`; return `${errorNum}\n${formatErrorWithColors(error, useColors)}`; }); return [header, ...formattedErrors].join('\n\n'); } export function formatAnyError(err: unknown, useColors: boolean = true): string { const parseError = toParseError(err); return formatErrorWithColors(parseError, useColors); } export function formatAnyErrors(errors: unknown[], useColors: boolean = true): string { if (!errors || errors.length === 0) return ''; const parseErrors = errors.map(toParseError); return formatMultipleErrors(parseErrors, useColors); } export function wrapCompilationError(err: unknown, context: string = 'Grammar compilation'): Error { const parseError = toParseError(err); const formattedError = formatErrorWithColors(parseError, true); return new Error(`${context} failed:\n${formattedError}`); } export function isGrammarError(err: unknown): boolean { if (typeof err === 'object' && err !== null) { const message = ('message' in err && typeof (err as Record<string, unknown>).message === 'string') ? (err as Record<string, unknown>).message : ('error' in err && typeof (err as Record<string, unknown>).error === 'string' ? (err as Record<string, unknown>).error : ''); return typeof message === 'string' && /expected.+found/i.test(message); } if (err instanceof Error) { return /expected.+found/i.test(err.message); } return false; } export function getErrorSuggestions(error: ParseError): string[] { const suggestions: string[] = []; const errorMsg = error.error?.toLowerCase() || ''; if (errorMsg.includes('expected') && errorMsg.includes('but')) { suggestions.push('Check for missing or incorrect syntax near the error location'); } if (errorMsg.includes('rule') || errorMsg.includes('undefined')) { suggestions.push('Verify all referenced rules are defined'); } if (errorMsg.includes('end of input')) { suggestions.push('Check for missing closing brackets, quotes, or semicolons'); } if (errorMsg.includes('duplicate')) { suggestions.push('Remove duplicate rule definitions'); } if (error.expected && error.expected.length > 0) { const expectedItems = error.expected.slice(0, 3).join(', '); suggestions.push(`Try using one of: ${expectedItems}`); } return suggestions; } export function formatErrorWithSuggestions(error: ParseError, useColors: boolean = true): string { const baseFormatted = formatErrorWithColors(error, useColors); const suggestions = getErrorSuggestions(error); if (suggestions.length === 0) { return baseFormatted; } const suggestionHeader = useColors ? colors.cyan('\n💡 Suggestions:') // FIX: Use colors.cyan : '\n💡 Suggestions:'; const formattedSuggestions = suggestions.map((suggestion, index) => { const bullet = useColors ? colors.dim(` ${index + 1}.`) : ` ${index + 1}.`; // FIX: Use colors.dim return `${bullet} ${suggestion}`; }).join('\n'); return `${baseFormatted}${suggestionHeader}\n${formattedSuggestions}`; } export function formatCompilationError(err: unknown, grammarSource?: string): string { const parseError = toParseError(err); if (grammarSource && !parseError.input) { parseError.input = grammarSource; } return formatErrorWithSuggestions(parseError, true); } export function getErrorContext(error: ParseError): { message: string; location?: string; line?: number; column?: number; expected?: string[]; found?: string | null; } { return { message: error.error || 'Unknown error', location: error.location ? formatLocation(error.location) : undefined, line: error.location?.start?.line, column: error.location?.start?.column, expected: error.expected, found: error.found }; } export function formatDebugError(err: unknown): string { const parseError = toParseError(err); const context = getErrorContext(parseError); const parts = [ `🐛 Debug Error Information:`, ` Message: ${context.message}`, ` Location: ${context.location || 'Unknown'}`, ` Expected: ${context.expected?.join(', ') || 'Unknown'}`, ` Found: ${context.found || 'Unknown'}`, ` Original Error Type: ${ typeof err === 'object' && err !== null && 'constructor' in err ? (err as { constructor: { name: string } }).constructor.name : typeof err }`, ` Has Location: ${!!parseError.location}`, ` Has Input: ${!!parseError.input}` ]; if (parseError.input && parseError.location) { parts.push(` Input Length: ${parseError.input.length}`); parts.push(` Error Position: ${parseError.location.start.line}:${parseError.location.start.column}`); } return parts.join('\n'); }