Skip to main content
Glama
cbunting99

MCP Code Analysis & Quality Server

by cbunting99
StructureAnalyzer.ts24.9 kB
// Copyright 2025 Chris Bunting // Brief: Structure Analyzer for Code Complexity Analyzer MCP Server // Scope: Analyzes code structure including design patterns, anti-patterns, and code duplication import { ASTNode } from './ASTProcessor.js'; import { Logger } from '../utils/Logger.js'; import { Language } from '@mcp-code-analysis/shared-types'; export interface DesignPattern { name: string; type: 'creational' | 'structural' | 'behavioral'; confidence: number; location: { line: number; column: number }; description: string; elements: string[]; } export interface AntiPattern { name: string; severity: 'low' | 'medium' | 'high' | 'critical'; confidence: number; location: { line: number; column: number }; description: string; suggestion: string; } export interface CodeDuplication { type: 'exact' | 'similar'; lines: number; locations: Array<{ filePath: string; startLine: number; endLine: number }>; similarity: number; } export interface StructureAnalysisResult { filePath: string; language: Language; designPatterns: DesignPattern[]; antiPatterns: AntiPattern[]; duplications: CodeDuplication[]; inheritanceHierarchy: InheritanceNode[]; interfaces: InterfaceInfo[]; analysisTime: number; errors: string[]; } export interface InheritanceNode { name: string; type: 'class' | 'interface'; parent?: string; children: string[]; methods: string[]; properties: string[]; location: { line: number; column: number }; } export interface InterfaceInfo { name: string; methods: string[]; properties: string[]; implementors: string[]; location: { line: number; column: number }; } export class StructureAnalyzer { private logger: Logger; constructor() { this.logger = new Logger(); } public async analyzeStructure( ast: ASTNode, filePath: string, language: Language, sourceCode?: string ): Promise<StructureAnalysisResult> { const startTime = Date.now(); const errors: string[] = []; try { // Analyze design patterns const designPatterns = this.detectDesignPatterns(ast, language); // Detect anti-patterns const antiPatterns = this.detectAntiPatterns(ast, language, sourceCode || ''); // Analyze code duplication (requires source code) const duplications = sourceCode ? this.analyzeCodeDuplication(sourceCode, filePath) : []; // Build inheritance hierarchy const inheritanceHierarchy = this.buildInheritanceHierarchy(ast, language); // Analyze interfaces const interfaces = this.analyzeInterfaces(ast, language); const analysisTime = Date.now() - startTime; this.logger.info(`Successfully analyzed structure for: ${filePath}`, { analysisTime, designPatterns: designPatterns.length, antiPatterns: antiPatterns.length, duplications: duplications.length }); return { filePath, language, designPatterns, antiPatterns, duplications, inheritanceHierarchy, interfaces, analysisTime, errors }; } catch (error) { const analysisTime = Date.now() - startTime; const errorMessage = error instanceof Error ? error.message : 'Unknown error'; errors.push(errorMessage); this.logger.error(`Failed to analyze structure for: ${filePath}`, { error: errorMessage }); return { filePath, language, designPatterns: [], antiPatterns: [], duplications: [], inheritanceHierarchy: [], interfaces: [], analysisTime, errors }; } } private detectDesignPatterns(ast: ASTNode, language: Language): DesignPattern[] { const patterns: DesignPattern[] = []; // Singleton Pattern const singletons = this.detectSingletonPattern(ast, language); patterns.push(...singletons); // Factory Pattern const factories = this.detectFactoryPattern(ast, language); patterns.push(...factories); // Observer Pattern const observers = this.detectObserverPattern(ast, language); patterns.push(...observers); // Strategy Pattern const strategies = this.detectStrategyPattern(ast, language); patterns.push(...strategies); // Decorator Pattern const decorators = this.detectDecoratorPattern(ast, language); patterns.push(...decorators); return patterns; } private detectSingletonPattern(ast: ASTNode, language: Language): DesignPattern[] { const singletons: DesignPattern[] = []; this.traverseAST(ast, (node) => { if (node.type === 'class') { const className = node.name; if (!className) return; // Look for singleton characteristics const hasPrivateConstructor = this.hasPrivateConstructor(node, language); const hasStaticInstance = this.hasStaticInstance(node, language); const hasGetInstanceMethod = this.hasGetInstanceMethod(node, language); if (hasPrivateConstructor && (hasStaticInstance || hasGetInstanceMethod)) { singletons.push({ name: 'Singleton', type: 'creational', confidence: 0.8, location: { line: node.start.line, column: node.start.column }, description: `Class ${className} appears to implement Singleton pattern`, elements: [className] }); } } }); return singletons; } private detectFactoryPattern(ast: ASTNode, language: Language): DesignPattern[] { const factories: DesignPattern[] = []; this.traverseAST(ast, (node) => { if (node.type === 'class') { const className = node.name; if (!className) return; // Look for factory characteristics const hasCreateMethods = this.hasCreateMethods(node, language); const hasFactoryInName = className.toLowerCase().includes('factory'); if (hasCreateMethods || hasFactoryInName) { factories.push({ name: 'Factory', type: 'creational', confidence: hasFactoryInName ? 0.9 : 0.7, location: { line: node.start.line, column: node.start.column }, description: `Class ${className} appears to implement Factory pattern`, elements: [className] }); } } }); return factories; } private detectObserverPattern(ast: ASTNode, language: Language): DesignPattern[] { const observers: DesignPattern[] = []; const subjects: string[] = []; const observersList: string[] = []; this.traverseAST(ast, (node) => { if (node.type === 'class') { const className = node.name; if (!className) return; const hasAttachMethods = this.hasAttachMethods(node, language); const hasNotifyMethods = this.hasNotifyMethods(node, language); const hasObserverInName = className.toLowerCase().includes('observer'); if (hasAttachMethods && hasNotifyMethods) { subjects.push(className); } if (hasObserverInName || this.hasUpdateMethods(node, language)) { observersList.push(className); } } }); // If we have both subjects and observers, it's likely the Observer pattern if (subjects.length > 0 && observersList.length > 0) { observers.push({ name: 'Observer', type: 'behavioral', confidence: 0.8, location: { line: 1, column: 0 }, description: 'Observer pattern detected with subjects and observers', elements: [...subjects, ...observersList] }); } return observers; } private detectStrategyPattern(ast: ASTNode, language: Language): DesignPattern[] { const strategies: DesignPattern[] = []; const strategyClasses: string[] = []; const contextClasses: string[] = []; this.traverseAST(ast, (node) => { if (node.type === 'class') { const className = node.name; if (!className) return; const hasStrategyInName = className.toLowerCase().includes('strategy'); const hasContextInName = className.toLowerCase().includes('context'); const hasAlgorithmMethods = this.hasAlgorithmMethods(node, language); if (hasStrategyInName || hasAlgorithmMethods) { strategyClasses.push(className); } if (hasContextInName) { contextClasses.push(className); } } }); if (strategyClasses.length > 0 && contextClasses.length > 0) { strategies.push({ name: 'Strategy', type: 'behavioral', confidence: 0.8, location: { line: 1, column: 0 }, description: 'Strategy pattern detected with strategy and context classes', elements: [...strategyClasses, ...contextClasses] }); } return strategies; } private detectDecoratorPattern(ast: ASTNode, language: Language): DesignPattern[] { const decorators: DesignPattern[] = []; const decoratorClasses: string[] = []; const componentClasses: string[] = []; this.traverseAST(ast, (node) => { if (node.type === 'class') { const className = node.name; if (!className) return; const hasDecoratorInName = className.toLowerCase().includes('decorator'); const hasComponentInName = className.toLowerCase().includes('component'); const wrapsOtherClasses = this.wrapsOtherClasses(node, language); if (hasDecoratorInName || wrapsOtherClasses) { decoratorClasses.push(className); } if (hasComponentInName) { componentClasses.push(className); } } }); if (decoratorClasses.length > 0 && componentClasses.length > 0) { decorators.push({ name: 'Decorator', type: 'structural', confidence: 0.8, location: { line: 1, column: 0 }, description: 'Decorator pattern detected with decorator and component classes', elements: [...decoratorClasses, ...componentClasses] }); } return decorators; } private detectAntiPatterns(ast: ASTNode, language: Language, sourceCode: string): AntiPattern[] { const antiPatterns: AntiPattern[] = []; // God Object anti-pattern const godObjects = this.detectGodObject(ast, language); antiPatterns.push(...godObjects); // Long Method anti-pattern const longMethods = this.detectLongMethod(ast, language); antiPatterns.push(...longMethods); // Large Class anti-pattern const largeClasses = this.detectLargeClass(ast, language); antiPatterns.push(...largeClasses); // Duplicated Code anti-pattern const duplicatedCode = this.detectDuplicatedCode(sourceCode); antiPatterns.push(...duplicatedCode); // Magic Numbers anti-pattern const magicNumbers = this.detectMagicNumbers(ast, sourceCode); antiPatterns.push(...magicNumbers); // Complex Conditional anti-pattern const complexConditionals = this.detectComplexConditional(ast, language); antiPatterns.push(...complexConditionals); return antiPatterns; } private detectGodObject(ast: ASTNode, _language: Language): AntiPattern[] { const godObjects: AntiPattern[] = []; this.traverseAST(ast, (node) => { if (node.type === 'class') { const className = node.name; if (!className) return; const methods = this.findFunctions(node); const properties = this.findProperties(node); // God object: too many responsibilities if (methods.length > 15 || properties.length > 20) { godObjects.push({ name: 'God Object', severity: 'high', confidence: 0.9, location: { line: node.start.line, column: node.start.column }, description: `Class ${className} has too many responsibilities (${methods.length} methods, ${properties.length} properties)`, suggestion: 'Consider splitting this class into smaller, more focused classes' }); } } }); return godObjects; } private detectLongMethod(ast: ASTNode, _language: Language): AntiPattern[] { const longMethods: AntiPattern[] = []; this.traverseAST(ast, (node) => { if ((node.type === 'function' || node.type === 'method') && node.name) { const lines = node.end.line - node.start.line + 1; // Long method: more than 20 lines if (lines > 20) { longMethods.push({ name: 'Long Method', severity: 'medium', confidence: 0.8, location: { line: node.start.line, column: node.start.column }, description: `Method ${node.name} is too long (${lines} lines)`, suggestion: 'Consider breaking this method into smaller, more focused methods' }); } } }); return longMethods; } private detectLargeClass(ast: ASTNode, _language: Language): AntiPattern[] { const largeClasses: AntiPattern[] = []; this.traverseAST(ast, (node) => { if (node.type === 'class') { const className = node.name; if (!className) return; const lines = node.end.line - node.start.line + 1; // Large class: more than 300 lines if (lines > 300) { largeClasses.push({ name: 'Large Class', severity: 'medium', confidence: 0.8, location: { line: node.start.line, column: node.start.column }, description: `Class ${className} is too large (${lines} lines)`, suggestion: 'Consider splitting this class into smaller, more focused classes' }); } } }); return largeClasses; } private detectDuplicatedCode(sourceCode: string): AntiPattern[] { const duplicatedCode: AntiPattern[] = []; const lines = sourceCode.split('\n'); // Simple duplication detection for (let i = 0; i < lines.length - 5; i++) { const block = lines.slice(i, i + 5).join('\n'); // Look for the same block elsewhere for (let j = i + 5; j < lines.length - 5; j++) { const otherBlock = lines.slice(j, j + 5).join('\n'); if (block === otherBlock && block.trim().length > 0) { duplicatedCode.push({ name: 'Duplicated Code', severity: 'medium', confidence: 0.7, location: { line: i + 1, column: 0 }, description: `Code duplication detected between lines ${i + 1}-${i + 5} and ${j + 1}-${j + 5}`, suggestion: 'Consider extracting the duplicated code into a separate method' }); } } } return duplicatedCode; } private detectMagicNumbers(_ast: ASTNode, sourceCode: string): AntiPattern[] { const magicNumbers: AntiPattern[] = []; const lines = sourceCode.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Look for magic numbers (numbers that aren't 0, 1, or 2) const numbers = line.match(/\b[3-9]\d*\b/g); if (numbers) { for (const number of numbers) { // Exclude common cases that aren't magic numbers if (!line.includes(`const ${number}`) && !line.includes(`#define ${number}`) && !line.includes('array') && !line.includes('size') && !line.includes('length')) { magicNumbers.push({ name: 'Magic Number', severity: 'low', confidence: 0.6, location: { line: i + 1, column: 0 }, description: `Magic number ${number} detected`, suggestion: 'Consider replacing magic numbers with named constants' }); } } } } return magicNumbers; } private detectComplexConditional(ast: ASTNode, _language: Language): AntiPattern[] { const complexConditionals: AntiPattern[] = []; this.traverseAST(ast, (node) => { if (node.type === 'IfStatement' || node.type === 'ConditionalExpression') { // Look for complex conditions (simplified) if (node.value && node.value.length > 100) { complexConditionals.push({ name: 'Complex Conditional', severity: 'medium', confidence: 0.7, location: { line: node.start.line, column: node.start.column }, description: 'Complex conditional logic detected', suggestion: 'Consider breaking down complex conditions into smaller, named methods' }); } } }); return complexConditionals; } private analyzeCodeDuplication(sourceCode: string, filePath: string): CodeDuplication[] { const duplications: CodeDuplication[] = []; const lines = sourceCode.split('\n'); const minBlockSize = 3; // Simple duplication detection for (let i = 0; i < lines.length - minBlockSize; i++) { for (let blockSize = minBlockSize; blockSize <= Math.min(10, lines.length - i); blockSize++) { const block = lines.slice(i, i + blockSize).join('\n'); // Look for the same block elsewhere for (let j = i + blockSize; j < lines.length - blockSize + 1; j++) { const otherBlock = lines.slice(j, j + blockSize).join('\n'); if (block === otherBlock && block.trim().length > 0) { duplications.push({ type: 'exact', lines: blockSize, locations: [ { filePath, startLine: i + 1, endLine: i + blockSize }, { filePath, startLine: j + 1, endLine: j + blockSize } ], similarity: 1.0 }); } } } } return duplications; } private buildInheritanceHierarchy(ast: ASTNode, _language: Language): InheritanceNode[] { const hierarchy: InheritanceNode[] = []; const classes = this.findClasses(ast); for (const cls of classes) { const className = cls.name || `anonymous_${cls.start.line}`; const methods = this.findFunctions(cls).map(f => f.name || ''); const properties = this.findProperties(cls); // Detect inheritance (simplified) let parent: string | undefined; if (cls.body) { for (const child of cls.body) { if (child.type === 'statement' && child.value) { const extendsMatch = child.value.match(/extends\s+(\w+)/); const implementsMatch = child.value.match(/implements\s+(\w+)/); if (extendsMatch) { parent = extendsMatch[1]; } else if (implementsMatch) { parent = implementsMatch[1]; } } } } hierarchy.push({ name: className, type: 'class', parent, children: [], methods, properties, location: { line: cls.start.line, column: cls.start.column } }); } // Build child relationships for (const node of hierarchy) { if (node.parent) { const parent = hierarchy.find(n => n.name === node.parent); if (parent) { parent.children.push(node.name); } } } return hierarchy; } private analyzeInterfaces(ast: ASTNode, _language: Language): InterfaceInfo[] { const interfaces: InterfaceInfo[] = []; this.traverseAST(ast, (node) => { if (node.type === 'class' && node.name) { const className = node.name; const methods = this.findFunctions(node).map(f => f.name || ''); const properties = this.findProperties(node); // Check if this looks like an interface const hasInterfaceInName = className.toLowerCase().includes('interface'); const hasAllAbstractMethods = this.hasAllAbstractMethods(node, _language); if (hasInterfaceInName || hasAllAbstractMethods) { interfaces.push({ name: className, methods, properties, implementors: [], // Would need cross-file analysis location: { line: node.start.line, column: node.start.column } }); } } }); return interfaces; } // Helper methods for pattern detection private hasPrivateConstructor(node: ASTNode, _language: Language): boolean { // Simplified implementation return node.body?.some(child => child.type === 'statement' && child.value?.includes('private') && child.value?.includes('constructor') ) || false; } private hasStaticInstance(node: ASTNode, _language: Language): boolean { // Simplified implementation return node.body?.some(child => child.type === 'statement' && child.value?.includes('static') && child.value?.includes('instance') ) || false; } private hasGetInstanceMethod(node: ASTNode, _language: Language): boolean { // Simplified implementation return node.body?.some(child => child.type === 'function' && child.name?.toLowerCase().includes('getinstance') ) || false; } private hasCreateMethods(node: ASTNode, _language: Language): boolean { // Simplified implementation return node.body?.some(child => child.type === 'function' && child.name?.toLowerCase().includes('create') ) || false; } private hasAttachMethods(node: ASTNode, _language: Language): boolean { // Simplified implementation return node.body?.some(child => child.type === 'function' && (child.name?.toLowerCase().includes('attach') || child.name?.toLowerCase().includes('addobserver') || child.name?.toLowerCase().includes('subscribe')) ) || false; } private hasNotifyMethods(node: ASTNode, _language: Language): boolean { // Simplified implementation return node.body?.some(child => child.type === 'function' && (child.name?.toLowerCase().includes('notify') || child.name?.toLowerCase().includes('update')) ) || false; } private hasUpdateMethods(node: ASTNode, _language: Language): boolean { // Simplified implementation return node.body?.some(child => child.type === 'function' && child.name?.toLowerCase().includes('update') ) || false; } private hasAlgorithmMethods(node: ASTNode, _language: Language): boolean { // Simplified implementation return node.body?.some(child => child.type === 'function' && (child.name?.toLowerCase().includes('execute') || child.name?.toLowerCase().includes('algorithm') || child.name?.toLowerCase().includes('compute')) ) || false; } private wrapsOtherClasses(node: ASTNode, _language: Language): boolean { // Simplified implementation return node.body?.some(child => child.type === 'statement' && child.value?.includes('wrapper') || child.value?.includes('decorate') ) || false; } private findClasses(ast: ASTNode): ASTNode[] { const classes: ASTNode[] = []; this.traverseAST(ast, (node) => { if (node.type === 'class') { classes.push(node); } }); return classes; } private findFunctions(node: ASTNode): ASTNode[] { const functions: ASTNode[] = []; this.traverseAST(node, (child) => { if (child.type === 'function' || child.type === 'method') { functions.push(child); } }); return functions; } private findProperties(node: ASTNode): string[] { const properties: string[] = []; this.traverseAST(node, (child) => { if (child.type === 'statement' && child.value) { // Look for property declarations const propMatches = child.value.match(/\b(?:private|public|protected)\s+(\w+)/g); if (propMatches) { propMatches.forEach((match: string) => { const prop = match.split(' ')[1]; if (prop) properties.push(prop); }); } } }); return properties; } private hasAllAbstractMethods(node: ASTNode, _language: Language): boolean { // Simplified implementation const methods = this.findFunctions(node); if (methods.length === 0) return false; return methods.every(method => method.body?.some(child => child.type === 'statement' && child.value?.includes('abstract') ) ); } private traverseAST(ast: ASTNode, visitor: (node: ASTNode) => void): void { visitor(ast); if (ast.body) { for (const child of ast.body) { this.traverseAST(child, visitor); } } if (ast.children) { for (const child of ast.children) { this.traverseAST(child, visitor); } } } }

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/cbunting99/mcp-code-analysis-server'

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