660 lines
22 KiB
C#
Executable File
660 lines
22 KiB
C#
Executable File
using MarketAlly.AIPlugin;
|
|
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp;
|
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace MarketAlly.AIPlugin.Analysis.Plugins
|
|
{
|
|
[AIPlugin("ComplexityAnalyzer", "Measures cyclomatic and cognitive complexity with refactoring suggestions")]
|
|
public class ComplexityAnalyzerPlugin : IAIPlugin
|
|
{
|
|
[AIParameter("Full path to the file or directory to analyze", required: true)]
|
|
public string Path { get; set; } = string.Empty;
|
|
|
|
[AIParameter("Calculate cyclomatic complexity", required: false)]
|
|
public bool CalculateCyclomatic { get; set; } = true;
|
|
|
|
[AIParameter("Calculate cognitive complexity", required: false)]
|
|
public bool CalculateCognitive { get; set; } = true;
|
|
|
|
[AIParameter("Maximum acceptable cyclomatic complexity", required: false)]
|
|
public int MaxCyclomaticComplexity { get; set; } = 10;
|
|
|
|
[AIParameter("Maximum acceptable cognitive complexity", required: false)]
|
|
public int MaxCognitiveComplexity { get; set; } = 15;
|
|
|
|
[AIParameter("Generate complexity reduction suggestions", required: false)]
|
|
public bool GenerateSuggestions { get; set; } = true;
|
|
|
|
[AIParameter("Include method-level complexity breakdown", required: false)]
|
|
public bool IncludeMethodBreakdown { get; set; } = true;
|
|
|
|
public IReadOnlyDictionary<string, Type> SupportedParameters => new Dictionary<string, Type>
|
|
{
|
|
["path"] = typeof(string),
|
|
["calculateCyclomatic"] = typeof(bool),
|
|
["calculateCognitive"] = typeof(bool),
|
|
["maxCyclomaticComplexity"] = typeof(int),
|
|
["maxCognitiveComplexity"] = typeof(int),
|
|
["generateSuggestions"] = typeof(bool),
|
|
["includeMethodBreakdown"] = typeof(bool)
|
|
};
|
|
|
|
public async Task<AIPluginResult> ExecuteAsync(IReadOnlyDictionary<string, object> parameters)
|
|
{
|
|
try
|
|
{
|
|
// Extract parameters
|
|
string path = parameters["path"]?.ToString() ?? string.Empty;
|
|
bool calculateCyclomatic = GetBoolParameter(parameters, "calculateCyclomatic", true);
|
|
bool calculateCognitive = GetBoolParameter(parameters, "calculateCognitive", true);
|
|
int maxCyclomatic = GetIntParameter(parameters, "maxCyclomaticComplexity", 10);
|
|
int maxCognitive = GetIntParameter(parameters, "maxCognitiveComplexity", 15);
|
|
bool generateSuggestions = GetBoolParameter(parameters, "generateSuggestions", true);
|
|
bool includeMethodBreakdown = GetBoolParameter(parameters, "includeMethodBreakdown", true);
|
|
|
|
// Validate path
|
|
if (!File.Exists(path) && !Directory.Exists(path))
|
|
{
|
|
return new AIPluginResult(
|
|
new FileNotFoundException($"Path not found: {path}"),
|
|
"Path not found"
|
|
);
|
|
}
|
|
|
|
// Get files to analyze
|
|
var filesToAnalyze = GetFilesToAnalyze(path);
|
|
if (!filesToAnalyze.Any())
|
|
{
|
|
return new AIPluginResult(
|
|
new InvalidOperationException("No C# files found to analyze"),
|
|
"No files found"
|
|
);
|
|
}
|
|
|
|
// Analyze complexity for each file
|
|
var fileResults = new List<FileComplexityResult>();
|
|
var overallMetrics = new ComplexityMetrics();
|
|
var highComplexityMethods = new List<MethodComplexityInfo>();
|
|
var violations = new List<ComplexityViolation>();
|
|
|
|
foreach (string filePath in filesToAnalyze)
|
|
{
|
|
var fileResult = await AnalyzeFileComplexity(
|
|
filePath, calculateCyclomatic, calculateCognitive,
|
|
maxCyclomatic, maxCognitive, includeMethodBreakdown);
|
|
|
|
fileResults.Add(fileResult);
|
|
overallMetrics.Add(fileResult.Metrics);
|
|
highComplexityMethods.AddRange(fileResult.HighComplexityMethods);
|
|
violations.AddRange(fileResult.Violations);
|
|
}
|
|
|
|
// Generate suggestions if requested
|
|
var suggestions = new List<string>();
|
|
if (generateSuggestions)
|
|
{
|
|
suggestions = GenerateComplexityReductionSuggestions(highComplexityMethods, violations);
|
|
}
|
|
|
|
// Calculate overall complexity score (0-100, higher is better)
|
|
int overallScore = CalculateOverallComplexityScore(overallMetrics, maxCyclomatic, maxCognitive);
|
|
|
|
var result = new
|
|
{
|
|
Path = path,
|
|
FilesAnalyzed = filesToAnalyze.Count,
|
|
CyclomaticComplexity = calculateCyclomatic ? new
|
|
{
|
|
Average = overallMetrics.AverageCyclomaticComplexity,
|
|
Maximum = overallMetrics.MaxCyclomaticComplexity,
|
|
Total = overallMetrics.TotalCyclomaticComplexity,
|
|
MethodsAboveThreshold = overallMetrics.CyclomaticViolations
|
|
} : null,
|
|
CognitiveComplexity = calculateCognitive ? new
|
|
{
|
|
Average = overallMetrics.AverageCognitiveComplexity,
|
|
Maximum = overallMetrics.MaxCognitiveComplexity,
|
|
Total = overallMetrics.TotalCognitiveComplexity,
|
|
MethodsAboveThreshold = overallMetrics.CognitiveViolations
|
|
} : null,
|
|
HighComplexityMethods = highComplexityMethods.Take(10).Select(m => new
|
|
{
|
|
m.MethodName,
|
|
m.ClassName,
|
|
m.FilePath,
|
|
m.LineNumber,
|
|
m.CyclomaticComplexity,
|
|
m.CognitiveComplexity,
|
|
m.ParameterCount,
|
|
m.LinesOfCode
|
|
}).ToList(),
|
|
ComplexityViolations = violations.Select(v => new
|
|
{
|
|
v.MethodName,
|
|
v.ClassName,
|
|
v.FilePath,
|
|
v.LineNumber,
|
|
v.ViolationType,
|
|
v.ActualValue,
|
|
v.ThresholdValue,
|
|
v.Severity
|
|
}).ToList(),
|
|
ReductionSuggestions = suggestions,
|
|
MethodBreakdown = includeMethodBreakdown ? fileResults.SelectMany(f => f.MethodDetails).ToList() : null,
|
|
OverallComplexityScore = overallScore,
|
|
Summary = new
|
|
{
|
|
TotalMethods = overallMetrics.TotalMethods,
|
|
HighComplexityMethods = highComplexityMethods.Count,
|
|
TotalViolations = violations.Count,
|
|
AverageMethodComplexity = Math.Round(overallMetrics.AverageCyclomaticComplexity, 2),
|
|
RecommendedActions = violations.Count > 0 ? "Refactor high-complexity methods" : "Complexity within acceptable limits"
|
|
}
|
|
};
|
|
|
|
return new AIPluginResult(result, $"Complexity analysis completed for {filesToAnalyze.Count} files");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new AIPluginResult(ex, "Failed to analyze complexity");
|
|
}
|
|
}
|
|
|
|
private async Task<FileComplexityResult> AnalyzeFileComplexity(
|
|
string filePath, bool calculateCyclomatic, bool calculateCognitive,
|
|
int maxCyclomatic, int maxCognitive, bool includeMethodBreakdown)
|
|
{
|
|
var sourceCode = await File.ReadAllTextAsync(filePath);
|
|
var syntaxTree = CSharpSyntaxTree.ParseText(sourceCode, path: filePath);
|
|
var root = await syntaxTree.GetRootAsync();
|
|
|
|
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>().ToList();
|
|
var constructors = root.DescendantNodes().OfType<ConstructorDeclarationSyntax>().ToList();
|
|
var properties = root.DescendantNodes().OfType<PropertyDeclarationSyntax>()
|
|
.Where(p => p.AccessorList?.Accessors.Any(a => a.Body != null || a.ExpressionBody != null) == true)
|
|
.ToList();
|
|
|
|
var result = new FileComplexityResult
|
|
{
|
|
FilePath = filePath,
|
|
FileName = System.IO.Path.GetFileName(filePath),
|
|
Metrics = new ComplexityMetrics(),
|
|
HighComplexityMethods = new List<MethodComplexityInfo>(),
|
|
Violations = new List<ComplexityViolation>(),
|
|
MethodDetails = new List<object>()
|
|
};
|
|
|
|
// Analyze methods
|
|
foreach (var method in methods)
|
|
{
|
|
var methodInfo = AnalyzeMethod(method, filePath, calculateCyclomatic, calculateCognitive);
|
|
result.Metrics.Add(methodInfo);
|
|
|
|
if (methodInfo.CyclomaticComplexity > maxCyclomatic || methodInfo.CognitiveComplexity > maxCognitive)
|
|
{
|
|
result.HighComplexityMethods.Add(methodInfo);
|
|
}
|
|
|
|
// Check for violations
|
|
if (calculateCyclomatic && methodInfo.CyclomaticComplexity > maxCyclomatic)
|
|
{
|
|
result.Violations.Add(new ComplexityViolation
|
|
{
|
|
MethodName = methodInfo.MethodName,
|
|
ClassName = methodInfo.ClassName,
|
|
FilePath = filePath,
|
|
LineNumber = methodInfo.LineNumber,
|
|
ViolationType = "CyclomaticComplexity",
|
|
ActualValue = methodInfo.CyclomaticComplexity,
|
|
ThresholdValue = maxCyclomatic,
|
|
Severity = methodInfo.CyclomaticComplexity > maxCyclomatic * 1.5 ? "High" : "Medium"
|
|
});
|
|
}
|
|
|
|
if (calculateCognitive && methodInfo.CognitiveComplexity > maxCognitive)
|
|
{
|
|
result.Violations.Add(new ComplexityViolation
|
|
{
|
|
MethodName = methodInfo.MethodName,
|
|
ClassName = methodInfo.ClassName,
|
|
FilePath = filePath,
|
|
LineNumber = methodInfo.LineNumber,
|
|
ViolationType = "CognitiveComplexity",
|
|
ActualValue = methodInfo.CognitiveComplexity,
|
|
ThresholdValue = maxCognitive,
|
|
Severity = methodInfo.CognitiveComplexity > maxCognitive * 1.5 ? "High" : "Medium"
|
|
});
|
|
}
|
|
|
|
if (includeMethodBreakdown)
|
|
{
|
|
result.MethodDetails.Add(new
|
|
{
|
|
methodInfo.MethodName,
|
|
methodInfo.ClassName,
|
|
methodInfo.CyclomaticComplexity,
|
|
methodInfo.CognitiveComplexity,
|
|
methodInfo.ParameterCount,
|
|
methodInfo.LinesOfCode,
|
|
methodInfo.LineNumber
|
|
});
|
|
}
|
|
}
|
|
|
|
// Analyze constructors
|
|
foreach (var constructor in constructors)
|
|
{
|
|
var methodInfo = AnalyzeConstructor(constructor, filePath, calculateCyclomatic, calculateCognitive);
|
|
result.Metrics.Add(methodInfo);
|
|
|
|
if (methodInfo.CyclomaticComplexity > maxCyclomatic || methodInfo.CognitiveComplexity > maxCognitive)
|
|
{
|
|
result.HighComplexityMethods.Add(methodInfo);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private MethodComplexityInfo AnalyzeMethod(MethodDeclarationSyntax method, string filePath,
|
|
bool calculateCyclomatic, bool calculateCognitive)
|
|
{
|
|
var className = GetContainingClassName(method);
|
|
var lineNumber = method.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
|
|
|
|
var info = new MethodComplexityInfo
|
|
{
|
|
MethodName = method.Identifier.ValueText,
|
|
ClassName = className,
|
|
FilePath = filePath,
|
|
LineNumber = lineNumber,
|
|
ParameterCount = method.ParameterList.Parameters.Count,
|
|
LinesOfCode = CalculateLinesOfCode(method)
|
|
};
|
|
|
|
if (calculateCyclomatic)
|
|
{
|
|
info.CyclomaticComplexity = CalculateCyclomaticComplexity(method);
|
|
}
|
|
|
|
if (calculateCognitive)
|
|
{
|
|
info.CognitiveComplexity = CalculateCognitiveComplexity(method);
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
private MethodComplexityInfo AnalyzeConstructor(ConstructorDeclarationSyntax constructor, string filePath,
|
|
bool calculateCyclomatic, bool calculateCognitive)
|
|
{
|
|
var className = GetContainingClassName(constructor);
|
|
var lineNumber = constructor.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
|
|
|
|
var info = new MethodComplexityInfo
|
|
{
|
|
MethodName = $"{className} (constructor)",
|
|
ClassName = className,
|
|
FilePath = filePath,
|
|
LineNumber = lineNumber,
|
|
ParameterCount = constructor.ParameterList.Parameters.Count,
|
|
LinesOfCode = CalculateLinesOfCode(constructor)
|
|
};
|
|
|
|
if (calculateCyclomatic)
|
|
{
|
|
info.CyclomaticComplexity = CalculateCyclomaticComplexity(constructor);
|
|
}
|
|
|
|
if (calculateCognitive)
|
|
{
|
|
info.CognitiveComplexity = CalculateCognitiveComplexity(constructor);
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
private int CalculateCyclomaticComplexity(SyntaxNode node)
|
|
{
|
|
int complexity = 1; // Base complexity
|
|
|
|
var descendants = node.DescendantNodes();
|
|
|
|
// Decision points that increase complexity
|
|
complexity += descendants.OfType<IfStatementSyntax>().Count();
|
|
complexity += descendants.OfType<WhileStatementSyntax>().Count();
|
|
complexity += descendants.OfType<ForStatementSyntax>().Count();
|
|
complexity += descendants.OfType<ForEachStatementSyntax>().Count();
|
|
complexity += descendants.OfType<DoStatementSyntax>().Count();
|
|
complexity += descendants.OfType<SwitchStatementSyntax>().Count();
|
|
complexity += descendants.OfType<SwitchExpressionSyntax>().Count();
|
|
complexity += descendants.OfType<ConditionalExpressionSyntax>().Count(); // Ternary operator
|
|
complexity += descendants.OfType<CatchClauseSyntax>().Count();
|
|
|
|
// Case statements in switch
|
|
foreach (var switchStmt in descendants.OfType<SwitchStatementSyntax>())
|
|
{
|
|
complexity += switchStmt.Sections.Count - 1; // Subtract 1 because switch already counted
|
|
}
|
|
|
|
// Switch expression arms
|
|
foreach (var switchExpr in descendants.OfType<SwitchExpressionSyntax>())
|
|
{
|
|
complexity += switchExpr.Arms.Count - 1; // Subtract 1 because switch already counted
|
|
}
|
|
|
|
// Logical operators (&& and ||)
|
|
var binaryExpressions = descendants.OfType<BinaryExpressionSyntax>();
|
|
foreach (var expr in binaryExpressions)
|
|
{
|
|
if (expr.OperatorToken.IsKind(SyntaxKind.AmpersandAmpersandToken) ||
|
|
expr.OperatorToken.IsKind(SyntaxKind.BarBarToken))
|
|
{
|
|
complexity++;
|
|
}
|
|
}
|
|
|
|
return complexity;
|
|
}
|
|
|
|
private int CalculateCognitiveComplexity(SyntaxNode node)
|
|
{
|
|
var calculator = new CognitiveComplexityCalculator();
|
|
return calculator.Calculate(node);
|
|
}
|
|
|
|
private int CalculateLinesOfCode(SyntaxNode node)
|
|
{
|
|
var span = node.GetLocation().GetLineSpan();
|
|
return span.EndLinePosition.Line - span.StartLinePosition.Line + 1;
|
|
}
|
|
|
|
private string GetContainingClassName(SyntaxNode node)
|
|
{
|
|
var classDeclaration = node.Ancestors().OfType<ClassDeclarationSyntax>().FirstOrDefault();
|
|
if (classDeclaration != null)
|
|
{
|
|
return classDeclaration.Identifier.ValueText;
|
|
}
|
|
|
|
var structDeclaration = node.Ancestors().OfType<StructDeclarationSyntax>().FirstOrDefault();
|
|
if (structDeclaration != null)
|
|
{
|
|
return structDeclaration.Identifier.ValueText;
|
|
}
|
|
|
|
return "Unknown";
|
|
}
|
|
|
|
private List<string> GetFilesToAnalyze(string path)
|
|
{
|
|
var files = new List<string>();
|
|
|
|
if (File.Exists(path))
|
|
{
|
|
if (path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
files.Add(path);
|
|
}
|
|
}
|
|
else if (Directory.Exists(path))
|
|
{
|
|
files.AddRange(Directory.GetFiles(path, "*.cs", SearchOption.AllDirectories));
|
|
}
|
|
|
|
return files;
|
|
}
|
|
|
|
private List<string> GenerateComplexityReductionSuggestions(
|
|
List<MethodComplexityInfo> highComplexityMethods,
|
|
List<ComplexityViolation> violations)
|
|
{
|
|
var suggestions = new List<string>();
|
|
|
|
if (!highComplexityMethods.Any())
|
|
{
|
|
suggestions.Add("✅ All methods have acceptable complexity levels.");
|
|
return suggestions;
|
|
}
|
|
|
|
// General suggestions
|
|
suggestions.Add("🔧 Consider the following complexity reduction strategies:");
|
|
|
|
// Method extraction suggestions
|
|
var methodsWithHighCyclomatic = highComplexityMethods.Where(m => m.CyclomaticComplexity > 15).ToList();
|
|
if (methodsWithHighCyclomatic.Any())
|
|
{
|
|
suggestions.Add($"📝 Extract smaller methods from {methodsWithHighCyclomatic.Count} method(s) with high cyclomatic complexity (>15)");
|
|
suggestions.Add(" • Break down large conditional blocks into separate methods");
|
|
suggestions.Add(" • Extract loop bodies into dedicated methods");
|
|
suggestions.Add(" • Use early returns to reduce nesting levels");
|
|
}
|
|
|
|
// Parameter count suggestions
|
|
var methodsWithManyParams = highComplexityMethods.Where(m => m.ParameterCount > 5).ToList();
|
|
if (methodsWithManyParams.Any())
|
|
{
|
|
suggestions.Add($"📦 Reduce parameter count for {methodsWithManyParams.Count} method(s) with >5 parameters");
|
|
suggestions.Add(" • Consider using parameter objects or DTOs");
|
|
suggestions.Add(" • Use builder pattern for complex object creation");
|
|
}
|
|
|
|
// Large method suggestions
|
|
var largeMethods = highComplexityMethods.Where(m => m.LinesOfCode > 50).ToList();
|
|
if (largeMethods.Any())
|
|
{
|
|
suggestions.Add($"📏 Break down {largeMethods.Count} large method(s) (>50 lines)");
|
|
suggestions.Add(" • Apply Single Responsibility Principle");
|
|
suggestions.Add(" • Extract helper methods for specific tasks");
|
|
}
|
|
|
|
// Specific method suggestions
|
|
var topComplexMethods = highComplexityMethods
|
|
.OrderByDescending(m => m.CyclomaticComplexity + m.CognitiveComplexity)
|
|
.Take(3);
|
|
|
|
suggestions.Add("🎯 Priority refactoring targets:");
|
|
foreach (var method in topComplexMethods)
|
|
{
|
|
suggestions.Add($" • {method.ClassName}.{method.MethodName} " +
|
|
$"(Cyclomatic: {method.CyclomaticComplexity}, Cognitive: {method.CognitiveComplexity})");
|
|
}
|
|
|
|
return suggestions;
|
|
}
|
|
|
|
private int CalculateOverallComplexityScore(ComplexityMetrics metrics, int maxCyclomatic, int maxCognitive)
|
|
{
|
|
if (metrics.TotalMethods == 0) return 100;
|
|
|
|
// Calculate percentage of methods within acceptable limits
|
|
var cyclomaticScore = metrics.TotalMethods > 0
|
|
? (double)(metrics.TotalMethods - metrics.CyclomaticViolations) / metrics.TotalMethods * 100
|
|
: 100;
|
|
|
|
var cognitiveScore = metrics.TotalMethods > 0
|
|
? (double)(metrics.TotalMethods - metrics.CognitiveViolations) / metrics.TotalMethods * 100
|
|
: 100;
|
|
|
|
// Weighted average (cognitive complexity is slightly more important)
|
|
var overallScore = (cyclomaticScore * 0.4 + cognitiveScore * 0.6);
|
|
|
|
// Penalty for extremely high complexity methods
|
|
var avgComplexity = metrics.AverageCyclomaticComplexity;
|
|
if (avgComplexity > maxCyclomatic * 2)
|
|
{
|
|
overallScore *= 0.7; // 30% penalty
|
|
}
|
|
else if (avgComplexity > maxCyclomatic * 1.5)
|
|
{
|
|
overallScore *= 0.85; // 15% penalty
|
|
}
|
|
|
|
return Math.Max(0, Math.Min(100, (int)Math.Round(overallScore)));
|
|
}
|
|
|
|
private bool GetBoolParameter(IReadOnlyDictionary<string, object> parameters, string key, bool defaultValue)
|
|
{
|
|
return parameters.TryGetValue(key, out var value) ? Convert.ToBoolean(value) : defaultValue;
|
|
}
|
|
|
|
private int GetIntParameter(IReadOnlyDictionary<string, object> parameters, string key, int defaultValue)
|
|
{
|
|
return parameters.TryGetValue(key, out var value) ? Convert.ToInt32(value) : defaultValue;
|
|
}
|
|
}
|
|
|
|
// Supporting classes for complexity analysis
|
|
public class ComplexityMetrics
|
|
{
|
|
public int TotalMethods { get; set; }
|
|
public int TotalCyclomaticComplexity { get; set; }
|
|
public int TotalCognitiveComplexity { get; set; }
|
|
public int MaxCyclomaticComplexity { get; set; }
|
|
public int MaxCognitiveComplexity { get; set; }
|
|
public int CyclomaticViolations { get; set; }
|
|
public int CognitiveViolations { get; set; }
|
|
|
|
public double AverageCyclomaticComplexity => TotalMethods > 0 ? (double)TotalCyclomaticComplexity / TotalMethods : 0;
|
|
public double AverageCognitiveComplexity => TotalMethods > 0 ? (double)TotalCognitiveComplexity / TotalMethods : 0;
|
|
|
|
public void Add(MethodComplexityInfo method)
|
|
{
|
|
TotalMethods++;
|
|
TotalCyclomaticComplexity += method.CyclomaticComplexity;
|
|
TotalCognitiveComplexity += method.CognitiveComplexity;
|
|
|
|
if (method.CyclomaticComplexity > MaxCyclomaticComplexity)
|
|
MaxCyclomaticComplexity = method.CyclomaticComplexity;
|
|
|
|
if (method.CognitiveComplexity > MaxCognitiveComplexity)
|
|
MaxCognitiveComplexity = method.CognitiveComplexity;
|
|
}
|
|
|
|
public void Add(ComplexityMetrics other)
|
|
{
|
|
TotalMethods += other.TotalMethods;
|
|
TotalCyclomaticComplexity += other.TotalCyclomaticComplexity;
|
|
TotalCognitiveComplexity += other.TotalCognitiveComplexity;
|
|
|
|
if (other.MaxCyclomaticComplexity > MaxCyclomaticComplexity)
|
|
MaxCyclomaticComplexity = other.MaxCyclomaticComplexity;
|
|
|
|
if (other.MaxCognitiveComplexity > MaxCognitiveComplexity)
|
|
MaxCognitiveComplexity = other.MaxCognitiveComplexity;
|
|
|
|
CyclomaticViolations += other.CyclomaticViolations;
|
|
CognitiveViolations += other.CognitiveViolations;
|
|
}
|
|
}
|
|
|
|
public class MethodComplexityInfo
|
|
{
|
|
public string MethodName { get; set; } = string.Empty;
|
|
public string ClassName { get; set; } = string.Empty;
|
|
public string FilePath { get; set; } = string.Empty;
|
|
public int LineNumber { get; set; }
|
|
public int CyclomaticComplexity { get; set; }
|
|
public int CognitiveComplexity { get; set; }
|
|
public int ParameterCount { get; set; }
|
|
public int LinesOfCode { get; set; }
|
|
}
|
|
|
|
public class ComplexityViolation
|
|
{
|
|
public string MethodName { get; set; } = string.Empty;
|
|
public string ClassName { get; set; } = string.Empty;
|
|
public string FilePath { get; set; } = string.Empty;
|
|
public int LineNumber { get; set; }
|
|
public string ViolationType { get; set; } = string.Empty;
|
|
public int ActualValue { get; set; }
|
|
public int ThresholdValue { get; set; }
|
|
public string Severity { get; set; } = string.Empty;
|
|
}
|
|
|
|
public class FileComplexityResult
|
|
{
|
|
public string FilePath { get; set; } = string.Empty;
|
|
public string FileName { get; set; } = string.Empty;
|
|
public ComplexityMetrics Metrics { get; set; } = new();
|
|
public List<MethodComplexityInfo> HighComplexityMethods { get; set; } = new();
|
|
public List<ComplexityViolation> Violations { get; set; } = new();
|
|
public List<object> MethodDetails { get; set; } = new();
|
|
}
|
|
|
|
// Cognitive Complexity Calculator (implements the cognitive complexity algorithm)
|
|
public class CognitiveComplexityCalculator
|
|
{
|
|
private int _complexity;
|
|
private int _nestingLevel;
|
|
|
|
public int Calculate(SyntaxNode node)
|
|
{
|
|
_complexity = 0;
|
|
_nestingLevel = 0;
|
|
Visit(node);
|
|
return _complexity;
|
|
}
|
|
|
|
private void Visit(SyntaxNode node)
|
|
{
|
|
switch (node)
|
|
{
|
|
case IfStatementSyntax _:
|
|
case WhileStatementSyntax _:
|
|
case ForStatementSyntax _:
|
|
case ForEachStatementSyntax _:
|
|
case DoStatementSyntax _:
|
|
_complexity += 1 + _nestingLevel;
|
|
_nestingLevel++;
|
|
VisitChildren(node);
|
|
_nestingLevel--;
|
|
break;
|
|
|
|
case SwitchStatementSyntax switchStmt:
|
|
_complexity += 1 + _nestingLevel;
|
|
_nestingLevel++;
|
|
VisitChildren(node);
|
|
_nestingLevel--;
|
|
break;
|
|
|
|
case ConditionalExpressionSyntax _:
|
|
_complexity += 1 + _nestingLevel;
|
|
VisitChildren(node);
|
|
break;
|
|
|
|
case CatchClauseSyntax _:
|
|
_complexity += 1 + _nestingLevel;
|
|
_nestingLevel++;
|
|
VisitChildren(node);
|
|
_nestingLevel--;
|
|
break;
|
|
|
|
case BinaryExpressionSyntax binary when
|
|
binary.OperatorToken.IsKind(SyntaxKind.AmpersandAmpersandToken) ||
|
|
binary.OperatorToken.IsKind(SyntaxKind.BarBarToken):
|
|
_complexity += 1;
|
|
VisitChildren(node);
|
|
break;
|
|
|
|
default:
|
|
VisitChildren(node);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void VisitChildren(SyntaxNode node)
|
|
{
|
|
foreach (var child in node.ChildNodes())
|
|
{
|
|
Visit(child);
|
|
}
|
|
}
|
|
}
|
|
} |