Skip to main content
Glama
apolosan

Design Patterns MCP Server

by apolosan
pattern-analyzer.ts14.3 kB
/** * Pattern Analyzer Service * Analyzes code snippets to detect design patterns and suggest improvements */ interface DetectedPattern { pattern: string; category: string; confidence: number; location?: { line?: number; column?: number; snippet?: string; }; indicators: string[]; } interface CodeAnalysisResult { identifiedPatterns: DetectedPattern[]; suggestedPatterns: { pattern: string; reason: string; confidence: number; }[]; improvements: string[]; antiPatterns?: { pattern: string; reason: string; severity: 'low' | 'medium' | 'high'; }[]; } export class PatternAnalyzer { // Pattern detection rules for various languages private readonly patternIndicators = { // Creational Patterns Singleton: { keywords: ['getInstance', 'instance', 'private constructor', '_instance', 'singleton'], patterns: [ /private\s+static\s+\w+\s+instance/i, /getInstance\s*\(\s*\)/i, /private\s+constructor\s*\(/i, /_instance\s*=\s*null/i, ], }, 'Factory Method': { keywords: ['createProduct', 'create', 'factory', 'makeProduct'], patterns: [ /create\w+\s*\([^)]*\)\s*:\s*\w+/i, /factory\s*\([^)]*\)\s*{/i, /abstract\s+\w+\s+create/i, /make\w+\s*\([^)]*\)/i, ], }, 'Abstract Factory': { keywords: ['createProductA', 'createProductB', 'AbstractFactory'], patterns: [ /abstract\s+class\s+\w*Factory/i, /createProduct[AB]\s*\(/i, /interface\s+\w*Factory/i, ], }, Builder: { keywords: ['build', 'Builder', 'withField', 'addPart'], patterns: [ /class\s+\w+Builder/i, /\.build\s*\(\s*\)/i, /with\w+\s*\([^)]*\)\s*:\s*this/i, /return\s+this/i, ], }, Prototype: { keywords: ['clone', 'copy', 'duplicate', 'prototype'], patterns: [ /clone\s*\(\s*\)\s*:/i, /Object\.create\(/i, /\.prototype\./i, /copy\s*\(\s*\)\s*:/i, ], }, // Structural Patterns Adapter: { keywords: ['adapt', 'Adapter', 'wrapper', 'convert'], patterns: [/class\s+\w+Adapter/i, /implements\s+\w+Interface/i, /this\.\w+\s*=\s*new\s+\w+/i], }, Decorator: { keywords: ['Decorator', 'wrapper', 'enhance', 'decorate'], patterns: [/class\s+\w+Decorator/i, /extends\s+\w+Decorator/i, /super\.\w+\(/i, /@\w+/], }, Proxy: { keywords: ['Proxy', 'surrogate', 'placeholder'], patterns: [/class\s+\w+Proxy/i, /new\s+Proxy\(/i, /realSubject/i, /this\.\w+\.\w+\(/i], }, Facade: { keywords: ['Facade', 'simplify', 'interface'], patterns: [/class\s+\w+Facade/i, /this\.\w+\s*=\s*new/i, /simplify\w+\(/i], }, // Behavioral Patterns Observer: { keywords: ['subscribe', 'notify', 'observer', 'listener', 'addEventListener'], patterns: [ /subscribe\s*\([^)]*\)/i, /notify\w*\s*\([^)]*\)/i, /addEventListener\s*\(/i, /on\w+\s*\([^)]*\)/i, /emit\s*\([^)]*\)/i, ], }, Strategy: { keywords: ['Strategy', 'algorithm', 'setStrategy', 'execute'], patterns: [ /class\s+\w+Strategy/i, /setStrategy\s*\(/i, /execute\w*\s*\(/i, /interface\s+Strategy/i, ], }, Command: { keywords: ['Command', 'execute', 'undo', 'redo'], patterns: [ /class\s+\w+Command/i, /execute\s*\(\s*\)/i, /undo\s*\(\s*\)/i, /interface\s+Command/i, ], }, Iterator: { keywords: ['Iterator', 'next', 'hasNext', 'current'], patterns: [ /class\s+\w+Iterator/i, /hasNext\s*\(\s*\)/i, /next\s*\(\s*\)/i, /\[Symbol\.iterator\]/i, ], }, 'Template Method': { keywords: ['template', 'abstract', 'hook', 'algorithm'], patterns: [/abstract\s+\w+\s*\(/i, /protected\s+abstract/i, /final\s+\w+\s*\(/i], }, }; // Anti-patterns to detect private readonly antiPatternIndicators = { 'God Object': { indicators: ['too many methods', 'too many properties', 'high complexity'], threshold: { methods: 20, properties: 30 }, }, 'Spaghetti Code': { indicators: ['deeply nested', 'goto', 'no structure'], threshold: { nestingLevel: 5 }, }, 'Copy-Paste Programming': { indicators: ['duplicate code', 'similar blocks'], threshold: { duplicateLines: 10 }, }, }; /** * Analyze code to detect patterns */ async analyzeCode(code: string, language: string): Promise<CodeAnalysisResult> { const identifiedPatterns = this.detectPatterns(code, language); const suggestedPatterns = this.suggestPatterns(code, language, identifiedPatterns); const improvements = this.generateImprovements(code, language, identifiedPatterns); const antiPatterns = this.detectAntiPatterns(code, language); return { identifiedPatterns, suggestedPatterns, improvements, antiPatterns, }; } /** * Detect existing patterns in code */ private detectPatterns(code: string, language: string): DetectedPattern[] { const detected: DetectedPattern[] = []; const codeLines = code.split('\n'); for (const [patternName, indicators] of Object.entries(this.patternIndicators)) { let confidence = 0; const foundIndicators: string[] = []; // Check keywords for (const keyword of indicators.keywords) { if (code.toLowerCase().includes(keyword.toLowerCase())) { confidence += 0.2; foundIndicators.push(`Found keyword: ${keyword}`); } } // Check regex patterns for (const pattern of indicators.patterns) { const match = code.match(pattern); if (match) { confidence += 0.3; foundIndicators.push(`Matched pattern: ${pattern.source}`); // Find line number const lineNum = this.findLineNumber(code, match.index || 0); if (lineNum > -1) { const location = { line: lineNum, snippet: codeLines[lineNum - 1]?.trim(), }; if (confidence > 0.4) { detected.push({ pattern: patternName, category: this.getPatternCategory(patternName), confidence: Math.min(confidence, 1.0), location, indicators: foundIndicators, }); } } } } } return detected; } /** * Suggest patterns that could improve the code */ private suggestPatterns( code: string, language: string, identifiedPatterns: DetectedPattern[] ): Array<{ pattern: string; reason: string; confidence: number }> { const suggestions = []; const identifiedNames = identifiedPatterns.map(p => p.pattern); // Suggest Singleton if there's global state management if (!identifiedNames.includes('Singleton')) { if (/global|static|instance/i.test(code)) { suggestions.push({ pattern: 'Singleton', reason: 'Consider Singleton pattern for managing global state', confidence: 0.6, }); } } // Suggest Factory if there's object creation logic if (!identifiedNames.includes('Factory Method')) { if (/new\s+\w+\(/g.test(code) && (code.match(/new\s+\w+\(/g) || []).length > 3) { suggestions.push({ pattern: 'Factory Method', reason: 'Multiple object instantiations detected - consider Factory pattern', confidence: 0.7, }); } } // Suggest Observer for event handling if (!identifiedNames.includes('Observer')) { if (/event|listener|callback|subscribe/i.test(code)) { suggestions.push({ pattern: 'Observer', reason: 'Event handling code detected - Observer pattern could improve decoupling', confidence: 0.6, }); } } // Suggest Strategy for conditional algorithms if (!identifiedNames.includes('Strategy')) { const ifCount = (code.match(/if\s*\(/g) || []).length; const switchCount = (code.match(/switch\s*\(/g) || []).length; if (ifCount > 5 || switchCount > 0) { suggestions.push({ pattern: 'Strategy', reason: 'Complex conditional logic detected - Strategy pattern could simplify', confidence: 0.5, }); } } // Suggest Builder for complex object construction if (!identifiedNames.includes('Builder')) { if (/constructor\s*\([^)]{50,}\)/i.test(code)) { suggestions.push({ pattern: 'Builder', reason: 'Complex constructor detected - Builder pattern could improve readability', confidence: 0.7, }); } } return suggestions; } /** * Generate improvement suggestions */ private generateImprovements( code: string, language: string, identifiedPatterns: DetectedPattern[] ): string[] { const improvements: string[] = []; // Check for missing error handling if (!/try\s*{|catch\s*\(|\.catch\s*\(/i.test(code)) { improvements.push('Add error handling to improve robustness'); } // Check for missing comments const codeLines = code.split('\n').length; const commentLines = (code.match(/\/\/|\/\*|\*/g) || []).length; if (commentLines < codeLines * 0.1) { improvements.push('Add more comments to improve code documentation'); } // Check for long methods const methodMatches = code.match(/function\s+\w+|[\w]+\s*\([^)]*\)\s*{/g) || []; if (methodMatches.length > 0) { const avgMethodLength = codeLines / methodMatches.length; if (avgMethodLength > 50) { improvements.push('Consider breaking down large methods into smaller, focused functions'); } } // Pattern-specific improvements for (const pattern of identifiedPatterns) { if (pattern.pattern === 'Singleton' && pattern.confidence < 0.8) { improvements.push('Singleton implementation could be improved with thread safety'); } if (pattern.pattern === 'Observer' && !/unsubscribe|removeListener/i.test(code)) { improvements.push('Observer pattern should include cleanup/unsubscribe mechanism'); } } // Check for hardcoded values if (/["']\d+["']|["']\w+@\w+\.\w+["']/g.test(code)) { improvements.push('Consider extracting hardcoded values to configuration'); } return improvements; } /** * Detect anti-patterns in code */ private detectAntiPatterns( code: string, language: string ): Array<{ pattern: string; reason: string; severity: 'low' | 'medium' | 'high' }> { const antiPatterns: Array<{ pattern: string; reason: string; severity: 'low' | 'medium' | 'high'; }> = []; // Check for God Object const methodCount = (code.match(/function\s+\w+|[\w]+\s*\([^)]*\)\s*{/g) || []).length; const propertyCount = (code.match(/this\.\w+\s*=/g) || []).length; if (methodCount > 20 || propertyCount > 30) { antiPatterns.push({ pattern: 'God Object', reason: `Class has ${methodCount} methods and ${propertyCount} properties - consider splitting responsibilities`, severity: 'high' as const, }); } // Check for deeply nested code (Spaghetti Code indicator) const maxNesting = this.calculateMaxNesting(code); if (maxNesting > 5) { antiPatterns.push({ pattern: 'Spaghetti Code', reason: `Code has nesting level of ${maxNesting} - consider refactoring to reduce complexity`, severity: maxNesting > 7 ? 'high' : ('medium' as const), }); } // Check for duplicate code blocks const duplicates = this.findDuplicateBlocks(code); if (duplicates.length > 0) { antiPatterns.push({ pattern: 'Copy-Paste Programming', reason: `Found ${duplicates.length} duplicate code blocks - consider extracting common functionality`, severity: duplicates.length > 3 ? 'high' : 'medium', }); } return antiPatterns; } /** * Helper: Get pattern category */ private getPatternCategory(patternName: string): string { const categories: Record<string, string[]> = { Creational: ['Singleton', 'Factory Method', 'Abstract Factory', 'Builder', 'Prototype'], Structural: ['Adapter', 'Bridge', 'Composite', 'Decorator', 'Facade', 'Flyweight', 'Proxy'], Behavioral: [ 'Chain of Responsibility', 'Command', 'Iterator', 'Mediator', 'Memento', 'Observer', 'State', 'Strategy', 'Template Method', 'Visitor', ], }; for (const [category, patterns] of Object.entries(categories)) { if (patterns.includes(patternName)) { return category; } } return 'Unknown'; } /** * Helper: Find line number for match */ private findLineNumber(code: string, index: number): number { const lines = code.substring(0, index).split('\n'); return lines.length; } /** * Helper: Calculate maximum nesting level */ private calculateMaxNesting(code: string): number { let maxNesting = 0; let currentNesting = 0; for (const char of code) { if (char === '{') { currentNesting++; maxNesting = Math.max(maxNesting, currentNesting); } else if (char === '}') { currentNesting = Math.max(0, currentNesting - 1); } } return maxNesting; } /** * Helper: Find duplicate code blocks */ private findDuplicateBlocks(code: string): string[] { const lines = code .split('\n') .map(l => l.trim()) .filter(l => l.length > 10); const duplicates: string[] = []; const seen = new Map<string, number>(); // Simple duplicate detection - could be improved with more sophisticated algorithms for (let i = 0; i < lines.length - 3; i++) { const block = lines.slice(i, i + 3).join('\n'); if (seen.has(block)) { duplicates.push(block); } else { seen.set(block, i); } } return [...new Set(duplicates)]; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/apolosan/design_patterns_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server