// 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)}`;
}
}