UNPKG

mushcode-mcp-server

Version:

A specialized Model Context Protocol server for MUSHCODE development assistance. Provides AI-powered code generation, validation, optimization, and examples for MUD development.

470 lines 19.1 kB
/** * MUSHCODE code formatting engine */ import { ValidationError } from '../utils/errors.js'; export class MushcodeFormatter { knowledgeBase; defaultIndentSize = 2; defaultLineLength = 80; constructor(knowledgeBase) { this.knowledgeBase = knowledgeBase; } /** * Format MUSHCODE according to specified style and options */ async format(request) { this.validateRequest(request); const style = request.style || 'readable'; const indentSize = request.indentSize ?? this.defaultIndentSize; const lineLength = request.lineLength ?? this.defaultLineLength; const preserveComments = request.preserveComments ?? true; // Get server dialect information const dialect = request.serverType ? this.knowledgeBase.dialects.get(request.serverType) : null; const changesMade = []; let formattedCode = request.code; // Apply formatting based on style switch (style) { case 'readable': formattedCode = this.applyReadableStyle(formattedCode, indentSize, lineLength, preserveComments, changesMade); break; case 'compact': formattedCode = this.applyCompactStyle(formattedCode, preserveComments, changesMade); break; case 'custom': formattedCode = this.applyCustomStyle(formattedCode, indentSize, lineLength, preserveComments, changesMade); break; } // Apply server-specific formatting if (dialect) { formattedCode = this.applyDialectFormatting(formattedCode, dialect, changesMade); } // Generate style notes const styleNotes = this.generateStyleNotes(style, indentSize, lineLength, preserveComments); return { formattedCode, changesMade, styleNotes }; } /** * Validate the formatting request */ validateRequest(request) { if (!request.code || typeof request.code !== 'string') { throw new ValidationError('Code is required and must be a string'); } if (request.code.trim().length === 0) { throw new ValidationError('Code cannot be empty'); } if (request.style && !['readable', 'compact', 'custom'].includes(request.style)) { throw new ValidationError('Style must be one of: readable, compact, custom'); } if (request.indentSize !== undefined) { if (typeof request.indentSize !== 'number' || request.indentSize < 0 || request.indentSize > 8) { throw new ValidationError('Indent size must be a number between 0 and 8'); } } if (request.lineLength !== undefined) { if (typeof request.lineLength !== 'number' || request.lineLength < 40 || request.lineLength > 200) { throw new ValidationError('Line length must be a number between 40 and 200'); } } if (request.serverType && !this.knowledgeBase.dialects.has(request.serverType)) { throw new ValidationError(`Unknown server type: ${request.serverType}`); } } /** * Apply readable formatting style */ applyReadableStyle(code, indentSize, lineLength, preserveComments, changesMade) { let formatted = code; // Normalize line endings formatted = formatted.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); // Remove excessive blank lines but preserve intentional spacing const originalBlankLines = (formatted.match(/\n\s*\n/g) || []).length; formatted = formatted.replace(/\n\s*\n\s*\n+/g, '\n\n'); const newBlankLines = (formatted.match(/\n\s*\n/g) || []).length; if (originalBlankLines !== newBlankLines) { changesMade.push('Normalized blank lines'); } // Add proper indentation formatted = this.addIndentation(formatted, indentSize, changesMade); // Format function calls and nested structures formatted = this.formatFunctionCalls(formatted, indentSize, changesMade); // Wrap long lines formatted = this.wrapLongLines(formatted, lineLength, indentSize, changesMade); // Format comments if preserving them if (preserveComments) { formatted = this.formatComments(formatted, changesMade); } else { const commentCount = (formatted.match(/@@[^\n]*/g) || []).length; formatted = formatted.replace(/@@[^\n]*/g, ''); if (commentCount > 0) { changesMade.push(`Removed ${commentCount} comments`); } } // Add spacing around operators formatted = this.addOperatorSpacing(formatted, changesMade); return formatted.trim(); } /** * Apply compact formatting style */ applyCompactStyle(code, preserveComments, changesMade) { let formatted = code; // Remove all unnecessary whitespace formatted = formatted.replace(/\s+/g, ' '); changesMade.push('Removed unnecessary whitespace'); // Remove blank lines formatted = formatted.replace(/\n\s*\n+/g, '\n'); changesMade.push('Removed blank lines'); // Remove comments unless preserving them if (!preserveComments) { const commentCount = (formatted.match(/@@[^\n]*/g) || []).length; formatted = formatted.replace(/@@[^\n]*/g, ''); if (commentCount > 0) { changesMade.push(`Removed ${commentCount} comments`); } } // Compact function calls formatted = this.compactFunctionCalls(formatted, changesMade); return formatted.trim(); } /** * Apply custom formatting style */ applyCustomStyle(code, indentSize, lineLength, preserveComments, changesMade) { // Custom style is a balanced approach between readable and compact let formatted = code; // Normalize whitespace but keep some structure formatted = formatted.replace(/[ \t]+/g, ' '); changesMade.push('Normalized whitespace'); // Add selective indentation for nested structures formatted = this.addSelectiveIndentation(formatted, indentSize, changesMade); // Format comments based on preference if (preserveComments) { formatted = this.formatComments(formatted, changesMade); } else { const commentCount = (formatted.match(/@@[^\n]*/g) || []).length; formatted = formatted.replace(/@@[^\n]*/g, ''); if (commentCount > 0) { changesMade.push(`Removed ${commentCount} comments`); } } // Wrap only extremely long lines if (lineLength > 100) { formatted = this.wrapLongLines(formatted, lineLength, indentSize, changesMade); } return formatted.trim(); } /** * Add proper indentation to code */ addIndentation(code, indentSize, changesMade) { const lines = code.split('\n'); const indentedLines = []; let currentIndent = 0; let indentationAdded = false; for (const line of lines) { const trimmedLine = line.trim(); if (trimmedLine.length === 0) { indentedLines.push(''); continue; } // Skip comment lines for indentation logic if (trimmedLine.startsWith('@@')) { indentedLines.push(line); continue; } // Decrease indent for closing braces/brackets if (trimmedLine.startsWith('}') || trimmedLine.startsWith(']')) { currentIndent = Math.max(0, currentIndent - 1); } // Add indentation const indent = ' '.repeat(currentIndent * indentSize); const originalLine = line; const indentedLine = indent + trimmedLine; if (originalLine.trim() !== trimmedLine || (originalLine.length > 0 && !originalLine.startsWith(indent))) { indentationAdded = true; } indentedLines.push(indentedLine); // Increase indent for opening braces/brackets and certain MUSHCODE constructs if (trimmedLine.endsWith('{') || trimmedLine.endsWith('[') || (trimmedLine.includes('switch(') && trimmedLine.includes('{'))) { currentIndent++; } } if (indentationAdded) { changesMade.push(`Added ${indentSize}-space indentation`); } return indentedLines.join('\n'); } /** * Format function calls for better readability */ formatFunctionCalls(code, indentSize, changesMade) { let formatted = code; let functionsFormatted = false; // Format complex function calls with multiple parameters const functionPattern = /(\w+)\(([^)]{20,})\)/g; formatted = formatted.replace(functionPattern, (match, funcName, params) => { if (params.includes(',')) { const paramList = params.split(',').map((p) => p.trim()); if (paramList.length > 2) { functionsFormatted = true; const indent = ' '.repeat(indentSize); return `${funcName}(\n${indent}${paramList.join(',\n' + indent)}\n)`; } } return match; }); if (functionsFormatted) { changesMade.push('Formatted complex function calls'); } return formatted; } /** * Wrap long lines */ wrapLongLines(code, lineLength, indentSize, changesMade) { const lines = code.split('\n'); const wrappedLines = []; let linesWrapped = false; for (const line of lines) { if (line.length <= lineLength) { wrappedLines.push(line); continue; } // Don't wrap comment lines if (line.trim().startsWith('@@')) { wrappedLines.push(line); continue; } // Try to wrap at logical points const wrapped = this.wrapLineAtLogicalPoints(line, lineLength, indentSize); if (wrapped.length > 1) { linesWrapped = true; wrappedLines.push(...wrapped); } else { wrappedLines.push(line); } } if (linesWrapped) { changesMade.push(`Wrapped lines longer than ${lineLength} characters`); } return wrappedLines.join('\n'); } /** * Wrap a line at logical points */ wrapLineAtLogicalPoints(line, lineLength, indentSize) { const indent = line.match(/^\s*/)?.[0] || ''; const content = line.substring(indent.length); // Try to break at commas, semicolons, or logical operators const breakPoints = [',', ';', '&', '|']; for (const breakPoint of breakPoints) { const parts = content.split(breakPoint); if (parts.length > 1) { const wrapped = []; let currentLine = indent + parts[0]; for (let i = 1; i < parts.length; i++) { const part = parts[i]; const addition = breakPoint + part; if (currentLine.length + addition.length <= lineLength) { currentLine += addition; } else { wrapped.push(currentLine); currentLine = indent + ' '.repeat(indentSize) + (part?.trim() || ''); } } if (currentLine.trim().length > 0) { wrapped.push(currentLine); } if (wrapped.length > 1) { return wrapped; } } } return [line]; } /** * Format comments for consistency */ formatComments(code, changesMade) { let formatted = code; let commentsFormatted = false; // Ensure comments start with @@ and have proper spacing formatted = formatted.replace(/^(\s*)@([^@\n])/gm, (_match, indent, content) => { commentsFormatted = true; return `${indent}@@ ${content}`; }); // Ensure single space after @@ and normalize multiple spaces within comments formatted = formatted.replace(/@@\s{2,}/g, '@@ '); formatted = formatted.replace(/@@\s+([^\n]*)/g, (_match, content) => { return `@@ ${content.replace(/\s+/g, ' ')}`; }); if (commentsFormatted) { changesMade.push('Formatted comment syntax'); } return formatted; } /** * Add spacing around operators */ addOperatorSpacing(code, changesMade) { let formatted = code; let spacingAdded = false; // Add spaces around comparison operators, but avoid breaking MUSHCODE syntax const operators = ['!=', '<=', '>=', '<', '>']; for (const op of operators) { const pattern = new RegExp(`(\\w)${op.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(\\w)`, 'g'); const replacement = `$1 ${op} $2`; const before = formatted; formatted = formatted.replace(pattern, replacement); if (before !== formatted) { spacingAdded = true; } } // Handle = operator more carefully to avoid breaking MUSHCODE syntax const equalPattern = /(eq\(%[^,]+),([^)]+)\)/g; const before = formatted; formatted = formatted.replace(equalPattern, '$1, $2)'); if (before !== formatted) { spacingAdded = true; } if (spacingAdded) { changesMade.push('Added spacing around operators'); } return formatted; } /** * Compact function calls for compact style */ compactFunctionCalls(code, changesMade) { let formatted = code; // Remove unnecessary spaces in function calls formatted = formatted.replace(/(\w+)\s*\(\s*/g, '$1('); formatted = formatted.replace(/\s*\)/g, ')'); formatted = formatted.replace(/,\s+/g, ','); changesMade.push('Compacted function calls'); return formatted; } /** * Add selective indentation for custom style */ addSelectiveIndentation(code, indentSize, changesMade) { const lines = code.split('\n'); const indentedLines = []; let indentationAdded = false; let inNestedContext = false; for (const line of lines) { const trimmedLine = line.trim(); if (trimmedLine.length === 0) { indentedLines.push(''); continue; } // Skip comment lines if (trimmedLine.startsWith('@@')) { indentedLines.push(line); continue; } // Check if we're entering a nested context if (trimmedLine.includes('switch(') || trimmedLine.includes('iter(')) { inNestedContext = true; indentedLines.push(line); continue; } // Check if we're in a nested context and should indent if (inNestedContext && (trimmedLine.startsWith('@pemit') || trimmedLine.startsWith('@halt'))) { if (!line.startsWith(' ')) { const indent = ' '.repeat(indentSize); indentedLines.push(indent + trimmedLine); indentationAdded = true; } else { indentedLines.push(line); } } else { indentedLines.push(line); // Reset nested context if we encounter a line that doesn't look nested if (!trimmedLine.includes('{') && !trimmedLine.includes('}')) { inNestedContext = false; } } } if (indentationAdded) { changesMade.push('Added selective indentation'); } return indentedLines.join('\n'); } /** * Apply server-specific dialect formatting */ applyDialectFormatting(code, dialect, changesMade) { let formatted = code; let dialectChanges = false; // Apply syntax variations specific to the dialect for (const rule of dialect.syntaxVariations) { if (rule.replacement) { const before = formatted; formatted = formatted.replace(new RegExp(rule.pattern, 'g'), rule.replacement); if (before !== formatted) { dialectChanges = true; } } } // Apply dialect-specific function formatting for (const func of dialect.functionLibrary) { // For now, we'll apply basic formatting based on function syntax if (func.syntax && func.syntax !== func.name) { const pattern = new RegExp(`\\b${func.name}\\b`, 'g'); const before = formatted; // Use the syntax as the preferred format if it's different from the name formatted = formatted.replace(pattern, func.syntax); if (before !== formatted) { dialectChanges = true; } } } if (dialectChanges) { changesMade.push(`Applied ${dialect.name} dialect formatting`); } return formatted; } /** * Generate style notes explaining formatting choices */ generateStyleNotes(style, indentSize, lineLength, preserveComments) { const notes = []; switch (style) { case 'readable': notes.push('Applied readable formatting style with proper indentation and line wrapping.'); notes.push(`Used ${indentSize} spaces for indentation and ${lineLength} character line length.`); break; case 'compact': notes.push('Applied compact formatting style to minimize code size.'); notes.push('Removed unnecessary whitespace and blank lines.'); break; case 'custom': notes.push('Applied custom formatting style with balanced readability and compactness.'); notes.push(`Used selective indentation with ${indentSize} spaces.`); break; } if (preserveComments) { notes.push('Comments were preserved and formatted for consistency.'); } else { notes.push('Comments were removed to reduce code size.'); } return notes.join(' '); } } //# sourceMappingURL=formatter.js.map