Skip to main content
Glama
cbunting99

MCP Code Analysis & Quality Server

by cbunting99
ComplexityAnalysisService.ts31 kB
// Copyright 2025 Chris Bunting // Brief: Main Complexity Analysis Service for MCP Server // Scope: Orchestrates all complexity analysis components and provides unified API import * as fs from 'fs'; import * as path from 'path'; import { ASTProcessor, ASTNode } from './ASTProcessor.js'; import { MetricCalculator, ComplexityMetrics, MetricCalculationResult } from './MetricCalculator.js'; import { StructureAnalyzer, StructureAnalysisResult } from './StructureAnalyzer.js'; import { MaintainabilityAssessor, MaintainabilityAssessment } from './MaintainabilityAssessor.js'; import { Logger } from '../utils/Logger.js'; import { AnalysisResult, AnalysisMetrics, ComplexityOptions, Language, SeverityLevel, IssueCategory } from '@mcp-code-analysis/shared-types'; export interface RefactoringSuggestion { type: 'extract_method' | 'extract_class' | 'rename' | 'simplify_conditional' | 'reduce_nesting'; priority: 'high' | 'medium' | 'low'; description: string; location: { line: number; column: number }; estimatedBenefit: string; effort: 'low' | 'medium' | 'high'; } export interface ComplexityHotspot { filePath: string; functionName?: string; className?: string; complexity: number; type: 'cyclomatic' | 'cognitive' | 'maintainability'; location: { line: number; column: number }; impact: 'high' | 'medium' | 'low'; } export interface TrendAnalysis { metric: string; timeframe: string; trend: 'improving' | 'stable' | 'declining'; currentValue: number; previousValue: number; changePercentage: number; dataPoints: Array<{ date: string; value: number }>; } export class ComplexityAnalysisService { private astProcessor: ASTProcessor; private metricCalculator: MetricCalculator; private structureAnalyzer: StructureAnalyzer; private maintainabilityAssessor: MaintainabilityAssessor; private logger: Logger; constructor( astProcessor: ASTProcessor, metricCalculator: MetricCalculator, structureAnalyzer: StructureAnalyzer, maintainabilityAssessor: MaintainabilityAssessor, logger: Logger ) { this.astProcessor = astProcessor; this.metricCalculator = metricCalculator; this.structureAnalyzer = structureAnalyzer; this.maintainabilityAssessor = maintainabilityAssessor; this.logger = logger; } public async analyzeComplexity( filePath: string, language?: Language, options: ComplexityOptions = {} ): Promise<AnalysisResult> { const startTime = Date.now(); this.logger.info(`Starting complexity analysis for: ${filePath}`); try { // Read source code const sourceCode = fs.readFileSync(filePath, 'utf-8'); // Process AST const astResult = await this.astProcessor.processFile(filePath, language); // Calculate metrics const metricResult = await this.metricCalculator.calculateMetrics( astResult.ast, filePath, sourceCode ); // Analyze structure const structureResult = await this.structureAnalyzer.analyzeStructure( astResult.ast, filePath, astResult.language, sourceCode ); // Assess maintainability const maintainabilityResult = await this.maintainabilityAssessor.assessMaintainability( astResult.ast, filePath, astResult.language, metricResult.metrics, sourceCode ); // Convert to AnalysisResult format const analysisResult = this.convertToAnalysisResult( filePath, astResult.language, metricResult, structureResult, maintainabilityResult, options ); const totalTime = Date.now() - startTime; this.logger.info(`Complexity analysis completed for: ${filePath}`, { processingTime: totalTime, cyclomaticComplexity: metricResult.metrics.cyclomaticComplexity, maintainabilityIndex: maintainabilityResult.overallScore }); return analysisResult; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; this.logger.error(`Complexity analysis failed for: ${filePath}`, { error: errorMessage }); // Return error result return { id: this.generateId(), timestamp: new Date(), filePath, language: language || Language.JAVASCRIPT, issues: [{ id: 'analysis_error', ruleId: 'complexity_analysis_error', message: `Analysis failed: ${errorMessage}`, severity: SeverityLevel.ERROR, line: 1, column: 1, category: IssueCategory.MAINTAINABILITY, tags: ['analysis', 'error'] }], metrics: this.getDefaultMetrics(), severity: SeverityLevel.ERROR }; } } public async suggestRefactorings( filePath: string, complexityThreshold: number = 10, focusArea: string = 'all' ): Promise<{ suggestions: RefactoringSuggestion[]; summary: { totalSuggestions: number; highPriority: number; mediumPriority: number; lowPriority: number }; analysisTime: number; }> { const startTime = Date.now(); this.logger.info(`Generating refactoring suggestions for: ${filePath}`); try { // Read source code and analyze const sourceCode = fs.readFileSync(filePath, 'utf-8'); const astResult = await this.astProcessor.processFile(filePath); const metricResult = await this.metricCalculator.calculateMetrics( astResult.ast, filePath, sourceCode ); const suggestions: RefactoringSuggestion[] = []; // Generate suggestions based on different complexity types if (focusArea === 'all' || focusArea === 'cyclomatic') { const cyclomaticSuggestions = this.generateCyclomaticRefactoringSuggestions( metricResult, complexityThreshold ); suggestions.push(...cyclomaticSuggestions); } if (focusArea === 'all' || focusArea === 'cognitive') { const cognitiveSuggestions = this.generateCognitiveRefactoringSuggestions( metricResult, complexityThreshold ); suggestions.push(...cognitiveSuggestions); } if (focusArea === 'all' || focusArea === 'maintainability') { const maintainabilitySuggestions = this.generateMaintainabilityRefactoringSuggestions( metricResult, complexityThreshold ); suggestions.push(...maintainabilitySuggestions); } // Sort suggestions by priority const priorityOrder = { high: 3, medium: 2, low: 1 }; suggestions.sort((a, b) => priorityOrder[b.priority] - priorityOrder[a.priority]); // Generate summary const summary = { totalSuggestions: suggestions.length, highPriority: suggestions.filter(s => s.priority === 'high').length, mediumPriority: suggestions.filter(s => s.priority === 'medium').length, lowPriority: suggestions.filter(s => s.priority === 'low').length }; const analysisTime = Date.now() - startTime; this.logger.info(`Refactoring suggestions generated for: ${filePath}`, { analysisTime, totalSuggestions: suggestions.length }); return { suggestions, summary, analysisTime }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; this.logger.error(`Failed to generate refactoring suggestions for: ${filePath}`, { error: errorMessage }); return { suggestions: [], summary: { totalSuggestions: 0, highPriority: 0, mediumPriority: 0, lowPriority: 0 }, analysisTime: Date.now() - startTime }; } } public async calculateMetrics( filePath: string, targetElements?: string[], customMetrics?: string[] ): Promise<{ overallMetrics: ComplexityMetrics; elementMetrics: Map<string, Partial<ComplexityMetrics>>; customMetrics: Map<string, number>; analysisTime: number; }> { const startTime = Date.now(); this.logger.info(`Calculating metrics for: ${filePath}`); try { const sourceCode = fs.readFileSync(filePath, 'utf-8'); const astResult = await this.astProcessor.processFile(filePath); // Calculate overall metrics const metricResult = await this.metricCalculator.calculateMetrics( astResult.ast, filePath, sourceCode ); // Filter element metrics if target elements are specified let elementMetrics = metricResult.functionMetrics; if (targetElements && targetElements.length > 0) { elementMetrics = new Map( Array.from(elementMetrics).filter(([name]) => targetElements.some(target => name.includes(target)) ) ); } // Calculate custom metrics const customMetricResults = new Map<string, number>(); if (customMetrics && customMetrics.length > 0) { for (const metric of customMetrics) { const value = this.calculateCustomMetric(metric, astResult.ast, sourceCode); customMetricResults.set(metric, value); } } const analysisTime = Date.now() - startTime; this.logger.info(`Metrics calculated for: ${filePath}`, { analysisTime, overallMetrics: Object.keys(metricResult.metrics).length, elementMetrics: elementMetrics.size, customMetrics: customMetricResults.size }); return { overallMetrics: metricResult.metrics, elementMetrics, customMetrics: customMetricResults, analysisTime }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; this.logger.error(`Failed to calculate metrics for: ${filePath}`, { error: errorMessage }); return { overallMetrics: this.metricCalculator['getDefaultMetrics'](), elementMetrics: new Map(), customMetrics: new Map(), analysisTime: Date.now() - startTime }; } } public async identifyHotspots( projectPath: string, filePatterns?: string[], hotspotThreshold: number = 15, maxResults: number = 20 ): Promise<{ hotspots: ComplexityHotspot[]; summary: { totalHotspots: number; highImpact: number; mediumImpact: number; lowImpact: number }; analysisTime: number; }> { const startTime = Date.now(); this.logger.info(`Identifying complexity hotspots in project: ${projectPath}`); try { const hotspots: ComplexityHotspot[] = []; // Find all relevant files const files = this.findProjectFiles(projectPath, filePatterns); // Analyze each file for (const filePath of files) { try { const sourceCode = fs.readFileSync(filePath, 'utf-8'); const astResult = await this.astProcessor.processFile(filePath); const metricResult = await this.metricCalculator.calculateMetrics( astResult.ast, filePath, sourceCode ); // Check for cyclomatic complexity hotspots if (metricResult.metrics.cyclomaticComplexity >= hotspotThreshold) { hotspots.push({ filePath, complexity: metricResult.metrics.cyclomaticComplexity, type: 'cyclomatic', location: { line: 1, column: 0 }, impact: this.calculateImpact(metricResult.metrics.cyclomaticComplexity) }); } // Check for cognitive complexity hotspots if (metricResult.metrics.cognitiveComplexity >= hotspotThreshold) { hotspots.push({ filePath, complexity: metricResult.metrics.cognitiveComplexity, type: 'cognitive', location: { line: 1, column: 0 }, impact: this.calculateImpact(metricResult.metrics.cognitiveComplexity) }); } // Check function-level hotspots for (const [funcName, funcMetrics] of metricResult.functionMetrics) { if (funcMetrics.cyclomaticComplexity && funcMetrics.cyclomaticComplexity >= hotspotThreshold) { hotspots.push({ filePath, functionName: funcName, complexity: funcMetrics.cyclomaticComplexity, type: 'cyclomatic', location: { line: 1, column: 0 }, // Would need actual location impact: this.calculateImpact(funcMetrics.cyclomaticComplexity) }); } } } catch (error) { this.logger.warn(`Failed to analyze file for hotspots: ${filePath}`, { error }); } } // Sort hotspots by complexity (highest first) hotspots.sort((a, b) => b.complexity - a.complexity); // Limit results const limitedHotspots = hotspots.slice(0, maxResults); // Generate summary const summary = { totalHotspots: limitedHotspots.length, highImpact: limitedHotspots.filter(h => h.impact === 'high').length, mediumImpact: limitedHotspots.filter(h => h.impact === 'medium').length, lowImpact: limitedHotspots.filter(h => h.impact === 'low').length }; const analysisTime = Date.now() - startTime; this.logger.info(`Complexity hotspots identified for project: ${projectPath}`, { analysisTime, totalHotspots: limitedHotspots.length, filesAnalyzed: files.length }); return { hotspots: limitedHotspots, summary, analysisTime }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; this.logger.error(`Failed to identify hotspots for project: ${projectPath}`, { error: errorMessage }); return { hotspots: [], summary: { totalHotspots: 0, highImpact: 0, mediumImpact: 0, lowImpact: 0 }, analysisTime: Date.now() - startTime }; } } public async trackTrends( projectPath: string, timeRange: string = 'month', metrics: string[] = ['cyclomaticComplexity', 'cognitiveComplexity', 'maintainabilityIndex'], includeFiles?: string[] ): Promise<{ trends: Map<string, TrendAnalysis>; summary: { improving: number; stable: number; declining: number }; analysisTime: number; }> { const startTime = Date.now(); this.logger.info(`Tracking complexity trends for project: ${projectPath}`); try { const trends = new Map<string, TrendAnalysis>(); // Find all relevant files const files = this.findProjectFiles(projectPath); if (includeFiles && includeFiles.length > 0) { includeFiles.forEach(includeFile => { const matchingFiles = files.filter(file => file.includes(includeFile)); files.splice(0, files.length, ...matchingFiles); }); } // For each metric, calculate current and previous values for (const metric of metrics) { const currentValues: number[] = []; const previousValues: number[] = []; // Analyze current state for (const filePath of files) { try { const sourceCode = fs.readFileSync(filePath, 'utf-8'); const astResult = await this.astProcessor.processFile(filePath); const metricResult = await this.metricCalculator.calculateMetrics( astResult.ast, filePath, sourceCode ); const value = this.getMetricValue(metricResult.metrics, metric); if (value !== undefined) { currentValues.push(value); } } catch (error) { this.logger.warn(`Failed to analyze file for trends: ${filePath}`, { error }); } } // Simulate previous values (in practice, would use historical data) previousValues.push(...currentValues.map(v => v * (0.9 + Math.random() * 0.2))); // Calculate averages const currentAvg = currentValues.reduce((sum, val) => sum + val, 0) / currentValues.length; const previousAvg = previousValues.reduce((sum, val) => sum + val, 0) / previousValues.length; // Determine trend const changePercentage = ((currentAvg - previousAvg) / previousAvg) * 100; let trend: 'improving' | 'stable' | 'declining'; if (metric === 'maintainabilityIndex') { trend = changePercentage > 2 ? 'improving' : changePercentage < -2 ? 'declining' : 'stable'; } else { trend = changePercentage < -2 ? 'improving' : changePercentage > 2 ? 'declining' : 'stable'; } // Generate simulated data points const dataPoints = this.generateTrendDataPoints(previousAvg, currentAvg, timeRange); trends.set(metric, { metric, timeframe: timeRange, trend, currentValue: currentAvg, previousValue: previousAvg, changePercentage, dataPoints }); } // Generate summary const trendArray = Array.from(trends.values()); const summary = { improving: trendArray.filter(t => t.trend === 'improving').length, stable: trendArray.filter(t => t.trend === 'stable').length, declining: trendArray.filter(t => t.trend === 'declining').length }; const analysisTime = Date.now() - startTime; this.logger.info(`Complexity trends tracked for project: ${projectPath}`, { analysisTime, metricsTracked: trends.size, filesAnalyzed: files.length }); return { trends, summary, analysisTime }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; this.logger.error(`Failed to track trends for project: ${projectPath}`, { error: errorMessage }); return { trends: new Map(), summary: { improving: 0, stable: 0, declining: 0 }, analysisTime: Date.now() - startTime }; } } // Private helper methods private convertToAnalysisResult( filePath: string, language: Language, metricResult: MetricCalculationResult, structureResult: StructureAnalysisResult, maintainabilityResult: MaintainabilityAssessment, options: ComplexityOptions ): AnalysisResult { const issues = []; // Add issues based on complexity thresholds const thresholds = options.thresholds || {}; if (metricResult.metrics.cyclomaticComplexity > (thresholds.cyclomatic || 10)) { issues.push({ id: 'high_cyclomatic_complexity', ruleId: 'cyclomatic_complexity', message: `High cyclomatic complexity: ${metricResult.metrics.cyclomaticComplexity}`, severity: SeverityLevel.WARNING, line: 1, column: 1, category: IssueCategory.MAINTAINABILITY, tags: ['complexity', 'cyclomatic'] }); } if (metricResult.metrics.cognitiveComplexity > (thresholds.cognitive || 15)) { issues.push({ id: 'high_cognitive_complexity', ruleId: 'cognitive_complexity', message: `High cognitive complexity: ${metricResult.metrics.cognitiveComplexity}`, severity: SeverityLevel.WARNING, line: 1, column: 1, category: IssueCategory.MAINTAINABILITY, tags: ['complexity', 'cognitive'] }); } if (maintainabilityResult.overallScore < (thresholds.maintainability || 60)) { issues.push({ id: 'low_maintainability', ruleId: 'maintainability_index', message: `Low maintainability index: ${maintainabilityResult.overallScore}`, severity: SeverityLevel.WARNING, line: 1, column: 1, category: IssueCategory.MAINTAINABILITY, tags: ['maintainability'] }); } // Add issues from anti-patterns for (const antiPattern of structureResult.antiPatterns) { const severity = this.mapAntiPatternSeverity(antiPattern.severity); issues.push({ id: `anti_pattern_${antiPattern.name.toLowerCase().replace(/\s+/g, '_')}`, ruleId: 'anti_pattern', message: antiPattern.description, severity, line: antiPattern.location.line, column: antiPattern.location.column, category: IssueCategory.MAINTAINABILITY, tags: ['anti-pattern', antiPattern.name.toLowerCase()] }); } // Convert metrics const analysisMetrics: AnalysisMetrics = { cyclomaticComplexity: metricResult.metrics.cyclomaticComplexity, cognitiveComplexity: metricResult.metrics.cognitiveComplexity, halsteadMetrics: metricResult.metrics.halsteadMetrics, linesOfCode: metricResult.metrics.linesOfCode, commentLines: metricResult.metrics.commentLines, maintainabilityIndex: maintainabilityResult.overallScore }; // Determine overall severity const overallSeverity = this.calculateOverallSeverity(issues, maintainabilityResult.overallScore); return { id: this.generateId(), timestamp: new Date(), filePath, language, issues, metrics: analysisMetrics, severity: overallSeverity }; } private generateCyclomaticRefactoringSuggestions( metricResult: MetricCalculationResult, threshold: number ): RefactoringSuggestion[] { const suggestions: RefactoringSuggestion[] = []; if (metricResult.metrics.cyclomaticComplexity > threshold) { suggestions.push({ type: 'extract_method', priority: metricResult.metrics.cyclomaticComplexity > 20 ? 'high' : 'medium', description: `Extract complex logic into smaller methods to reduce cyclomatic complexity from ${metricResult.metrics.cyclomaticComplexity}`, location: { line: 1, column: 0 }, estimatedBenefit: `Reduce cyclomatic complexity by ${Math.floor(metricResult.metrics.cyclomaticComplexity * 0.3)} points`, effort: 'medium' }); } // Check function-level complexities for (const [funcName, funcMetrics] of metricResult.functionMetrics) { if (funcMetrics.cyclomaticComplexity && funcMetrics.cyclomaticComplexity > threshold) { suggestions.push({ type: 'extract_method', priority: funcMetrics.cyclomaticComplexity > 20 ? 'high' : 'medium', description: `Function '${funcName}' has high cyclomatic complexity (${funcMetrics.cyclomaticComplexity}). Consider extracting parts into separate methods.`, location: { line: 1, column: 0 }, estimatedBenefit: `Improve readability and testability of ${funcName}`, effort: 'medium' }); } } return suggestions; } private generateCognitiveRefactoringSuggestions( metricResult: MetricCalculationResult, threshold: number ): RefactoringSuggestion[] { const suggestions: RefactoringSuggestion[] = []; if (metricResult.metrics.cognitiveComplexity > threshold) { suggestions.push({ type: 'simplify_conditional', priority: metricResult.metrics.cognitiveComplexity > 30 ? 'high' : 'medium', description: `Simplify complex conditional logic to reduce cognitive complexity from ${metricResult.metrics.cognitiveComplexity}`, location: { line: 1, column: 0 }, estimatedBenefit: `Make code easier to understand and maintain`, effort: 'medium' }); } if (metricResult.metrics.nestingDepth > 5) { suggestions.push({ type: 'reduce_nesting', priority: 'medium', description: `Reduce nesting depth (${metricResult.metrics.nestingDepth}) by using early returns or extracting methods`, location: { line: 1, column: 0 }, estimatedBenefit: `Improve code readability and reduce cognitive load`, effort: 'low' }); } return suggestions; } private generateMaintainabilityRefactoringSuggestions( metricResult: MetricCalculationResult, _threshold: number ): RefactoringSuggestion[] { const suggestions: RefactoringSuggestion[] = []; if (metricResult.metrics.coupling > 10) { suggestions.push({ type: 'extract_class', priority: 'medium', description: `High coupling (${metricResult.metrics.coupling}) detected. Consider extracting related functionality into separate classes.`, location: { line: 1, column: 0 }, estimatedBenefit: `Reduce dependencies and improve modularity`, effort: 'high' }); } if (metricResult.metrics.averageFunctionSize > 50) { suggestions.push({ type: 'extract_method', priority: 'medium', description: `Large average function size (${metricResult.metrics.averageFunctionSize.toFixed(1)} lines). Consider breaking down large functions.`, location: { line: 1, column: 0 }, estimatedBenefit: `Improve maintainability and reusability`, effort: 'medium' }); } return suggestions; } private calculateCustomMetric(metric: string, ast: ASTNode, sourceCode: string): number { switch (metric.toLowerCase()) { case 'comment_ratio': const lines = sourceCode.split('\n'); const commentLines = lines.filter(line => line.trim().startsWith('//') || line.trim().startsWith('#') || line.trim().startsWith('/*') || line.trim().startsWith('*') ).length; return lines.length > 0 ? commentLines / lines.length : 0; case 'average_function_length': const functions = this.findFunctions(ast); if (functions.length === 0) return 0; const totalLines = functions.reduce((sum, func) => sum + (func.end.line - func.start.line + 1), 0); return totalLines / functions.length; case 'inheritance_depth': return this.calculateInheritanceDepth(ast); default: return 0; } } private findProjectFiles(projectPath: string, patterns?: string[]): string[] { const files: string[] = []; if (!fs.existsSync(projectPath)) { return files; } const extensions = ['.js', '.ts', '.py', '.java', '.c', '.cpp', '.go', '.rs']; const scanDirectory = (dir: string) => { const items = fs.readdirSync(dir); for (const item of items) { const fullPath = path.join(dir, item); const stat = fs.statSync(fullPath); if (stat.isDirectory()) { scanDirectory(fullPath); } else if (stat.isFile()) { const ext = path.extname(item).toLowerCase(); if (extensions.includes(ext)) { if (!patterns || patterns.length === 0 || patterns.some(pattern => item.includes(pattern) || fullPath.includes(pattern) )) { files.push(fullPath); } } } } }; scanDirectory(projectPath); return files; } private calculateImpact(complexity: number): 'high' | 'medium' | 'low' { if (complexity > 25) return 'high'; if (complexity > 15) return 'medium'; return 'low'; } private getMetricValue(metrics: ComplexityMetrics, metricName: string): number | undefined { switch (metricName) { case 'cyclomaticComplexity': return metrics.cyclomaticComplexity; case 'cognitiveComplexity': return metrics.cognitiveComplexity; case 'maintainabilityIndex': return metrics.maintainabilityIndex; case 'linesOfCode': return metrics.linesOfCode; case 'nestingDepth': return metrics.nestingDepth; default: return undefined; } } private generateTrendDataPoints(previousValue: number, currentValue: number, timeRange: string): Array<{ date: string; value: number }> { const dataPoints: Array<{ date: string; value: number }> = []; const now = new Date(); let dataPointCount: number; switch (timeRange) { case 'week': dataPointCount = 7; break; case 'month': dataPointCount = 30; break; case 'quarter': dataPointCount = 90; break; case 'year': dataPointCount = 365; break; default: dataPointCount = 30; } for (let i = dataPointCount - 1; i >= 0; i--) { const date = new Date(now.getTime() - i * 24 * 60 * 60 * 1000); const progress = (dataPointCount - i) / dataPointCount; const value = previousValue + (currentValue - previousValue) * progress; dataPoints.push({ date: date.toISOString().split('T')[0], value: Math.round(value * 100) / 100 }); } return dataPoints; } private mapAntiPatternSeverity(severity: string): SeverityLevel { switch (severity) { case 'critical': return SeverityLevel.ERROR; case 'high': return SeverityLevel.WARNING; case 'medium': return SeverityLevel.INFO; case 'low': return SeverityLevel.HINT; default: return SeverityLevel.INFO; } } private calculateOverallSeverity(issues: any[], maintainabilityScore: number): SeverityLevel { if (issues.some(issue => issue.severity === SeverityLevel.ERROR) || maintainabilityScore < 30) { return SeverityLevel.ERROR; } if (issues.some(issue => issue.severity === SeverityLevel.WARNING) || maintainabilityScore < 60) { return SeverityLevel.WARNING; } if (issues.some(issue => issue.severity === SeverityLevel.INFO) || maintainabilityScore < 80) { return SeverityLevel.INFO; } return SeverityLevel.HINT; } private getDefaultMetrics(): AnalysisMetrics { return { cyclomaticComplexity: 1, cognitiveComplexity: 0, linesOfCode: 0, commentLines: 0, maintainabilityIndex: 100 }; } private findFunctions(ast: ASTNode): ASTNode[] { const functions: ASTNode[] = []; const traverse = (node: ASTNode) => { if (node.type === 'function' || node.type === 'method') { functions.push(node); } if (node.body) { node.body.forEach(traverse); } if (node.children) { node.children.forEach(traverse); } }; traverse(ast); return functions; } private calculateInheritanceDepth(ast: ASTNode): number { let maxDepth = 0; const traverse = (node: ASTNode, depth: number) => { if (node.type === 'class') { maxDepth = Math.max(maxDepth, depth); } if (node.body) { node.body.forEach(child => traverse(child, depth + 1)); } if (node.children) { node.children.forEach(child => traverse(child, depth + 1)); } }; traverse(ast, 0); return maxDepth; } private generateId(): string { return `analysis_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } }

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