1323 lines
45 KiB
C#
Executable File
1323 lines
45 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.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace MarketAlly.AIPlugin.Analysis.Plugins
|
|
{
|
|
[AIPlugin("PerformanceAnalyzer", "Identifies performance bottlenecks and optimization opportunities in code")]
|
|
public class PerformanceAnalyzerPlugin : IAIPlugin
|
|
{
|
|
[AIParameter("Full path to the file or directory to analyze", required: true)]
|
|
public string Path { get; set; } = string.Empty;
|
|
|
|
[AIParameter("Analyze algorithm complexity", required: false)]
|
|
public bool AnalyzeComplexity { get; set; } = true;
|
|
|
|
[AIParameter("Check for inefficient loops and iterations", required: false)]
|
|
public bool CheckLoops { get; set; } = true;
|
|
|
|
[AIParameter("Analyze memory allocation patterns", required: false)]
|
|
public bool AnalyzeMemory { get; set; } = true;
|
|
|
|
[AIParameter("Check for database query optimization opportunities", required: false)]
|
|
public bool CheckDatabase { get; set; } = true;
|
|
|
|
[AIParameter("Suggest caching opportunities", required: false)]
|
|
public bool SuggestCaching { get; set; } = true;
|
|
|
|
[AIParameter("Performance analysis depth: basic, detailed, comprehensive", required: false)]
|
|
public string AnalysisDepth { get; set; } = "detailed";
|
|
|
|
public IReadOnlyDictionary<string, Type> SupportedParameters => new Dictionary<string, Type>
|
|
{
|
|
["path"] = typeof(string),
|
|
["analyzeComplexity"] = typeof(bool),
|
|
["checkLoops"] = typeof(bool),
|
|
["analyzeMemory"] = typeof(bool),
|
|
["checkDatabase"] = typeof(bool),
|
|
["suggestCaching"] = typeof(bool),
|
|
["analysisDepth"] = typeof(string)
|
|
};
|
|
|
|
public async Task<AIPluginResult> ExecuteAsync(IReadOnlyDictionary<string, object> parameters)
|
|
{
|
|
try
|
|
{
|
|
// Extract parameters
|
|
string path = parameters["path"]?.ToString() ?? string.Empty;
|
|
bool analyzeComplexity = GetBoolParameter(parameters, "analyzeComplexity", true);
|
|
bool checkLoops = GetBoolParameter(parameters, "checkLoops", true);
|
|
bool analyzeMemory = GetBoolParameter(parameters, "analyzeMemory", true);
|
|
bool checkDatabase = GetBoolParameter(parameters, "checkDatabase", true);
|
|
bool suggestCaching = GetBoolParameter(parameters, "suggestCaching", true);
|
|
string analysisDepth = parameters.TryGetValue("analysisDepth", out var depth)
|
|
? depth?.ToString() ?? "detailed"
|
|
: "detailed";
|
|
|
|
// 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"
|
|
);
|
|
}
|
|
|
|
// Initialize analyzers based on depth
|
|
var analyzers = GetAnalyzersForDepth(analysisDepth);
|
|
|
|
// Analyze performance for each file
|
|
var performanceIssues = new List<PerformanceIssue>();
|
|
var optimizationOpportunities = new List<OptimizationOpportunity>();
|
|
var cachingOpportunities = new List<CachingOpportunity>();
|
|
var complexityIssues = new List<ComplexityIssue>();
|
|
var loopOptimizations = new List<LoopOptimization>();
|
|
var memoryOptimizations = new List<MemoryOptimization>();
|
|
var databaseOptimizations = new List<DatabaseOptimization>();
|
|
|
|
foreach (string filePath in filesToAnalyze)
|
|
{
|
|
var fileResult = await AnalyzeFilePerformance(
|
|
filePath, analyzeComplexity, checkLoops, analyzeMemory,
|
|
checkDatabase, suggestCaching, analyzers);
|
|
|
|
performanceIssues.AddRange(fileResult.PerformanceIssues);
|
|
optimizationOpportunities.AddRange(fileResult.OptimizationOpportunities);
|
|
cachingOpportunities.AddRange(fileResult.CachingOpportunities);
|
|
complexityIssues.AddRange(fileResult.ComplexityIssues);
|
|
loopOptimizations.AddRange(fileResult.LoopOptimizations);
|
|
memoryOptimizations.AddRange(fileResult.MemoryOptimizations);
|
|
databaseOptimizations.AddRange(fileResult.DatabaseOptimizations);
|
|
}
|
|
|
|
// Calculate performance score (0-100, higher is better)
|
|
int performanceScore = CalculatePerformanceScore(
|
|
performanceIssues, optimizationOpportunities, filesToAnalyze.Count);
|
|
|
|
// Generate recommendations
|
|
var recommendations = GeneratePerformanceRecommendations(
|
|
performanceIssues, optimizationOpportunities, cachingOpportunities);
|
|
|
|
var result = new
|
|
{
|
|
Path = path,
|
|
FilesAnalyzed = filesToAnalyze.Count,
|
|
AnalysisDepth = analysisDepth,
|
|
PerformanceScore = performanceScore,
|
|
ComplexityIssues = analyzeComplexity ? complexityIssues.Select(i => new
|
|
{
|
|
i.MethodName,
|
|
i.ClassName,
|
|
i.FilePath,
|
|
i.LineNumber,
|
|
i.AlgorithmicComplexity,
|
|
i.Description,
|
|
i.Severity,
|
|
i.RecommendedAction
|
|
}).ToList() : null,
|
|
LoopOptimizations = checkLoops ? loopOptimizations.Select(l => new
|
|
{
|
|
l.MethodName,
|
|
l.ClassName,
|
|
l.FilePath,
|
|
l.LineNumber,
|
|
l.LoopType,
|
|
l.IssueType,
|
|
l.Description,
|
|
l.Severity,
|
|
l.Suggestion
|
|
}).ToList() : null,
|
|
MemoryOptimizations = analyzeMemory ? memoryOptimizations.Select(m => new
|
|
{
|
|
m.MethodName,
|
|
m.ClassName,
|
|
m.FilePath,
|
|
m.LineNumber,
|
|
m.IssueType,
|
|
m.Description,
|
|
m.EstimatedImpact,
|
|
m.Suggestion
|
|
}).ToList() : null,
|
|
DatabaseOptimizations = checkDatabase ? databaseOptimizations.Select(d => new
|
|
{
|
|
d.MethodName,
|
|
d.ClassName,
|
|
d.FilePath,
|
|
d.LineNumber,
|
|
d.QueryType,
|
|
d.IssueType,
|
|
d.Description,
|
|
d.Severity,
|
|
d.Suggestion
|
|
}).ToList() : null,
|
|
CachingOpportunities = suggestCaching ? cachingOpportunities.Select(c => new
|
|
{
|
|
c.MethodName,
|
|
c.ClassName,
|
|
c.FilePath,
|
|
c.LineNumber,
|
|
c.CacheType,
|
|
c.Rationale,
|
|
c.EstimatedBenefit,
|
|
c.Implementation
|
|
}).ToList() : null,
|
|
Recommendations = recommendations,
|
|
Summary = new
|
|
{
|
|
TotalIssues = performanceIssues.Count,
|
|
HighSeverityIssues = performanceIssues.Count(i => i.Severity == "High"),
|
|
OptimizationOpportunities = optimizationOpportunities.Count,
|
|
CachingOpportunities = cachingOpportunities.Count,
|
|
EstimatedPerformanceGain = CalculateEstimatedGain(optimizationOpportunities),
|
|
PriorityActions = GetPriorityActions(performanceIssues, optimizationOpportunities)
|
|
}
|
|
};
|
|
|
|
return new AIPluginResult(result,
|
|
$"Performance analysis completed for {filesToAnalyze.Count} files. " +
|
|
$"Found {performanceIssues.Count} issues and {optimizationOpportunities.Count} optimization opportunities.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new AIPluginResult(ex, "Failed to analyze performance");
|
|
}
|
|
}
|
|
|
|
private async Task<FilePerformanceResult> AnalyzeFilePerformance(
|
|
string filePath, bool analyzeComplexity, bool checkLoops, bool analyzeMemory,
|
|
bool checkDatabase, bool suggestCaching, List<IPerformanceAnalyzer> analyzers)
|
|
{
|
|
var sourceCode = await File.ReadAllTextAsync(filePath);
|
|
var syntaxTree = CSharpSyntaxTree.ParseText(sourceCode, path: filePath);
|
|
var root = await syntaxTree.GetRootAsync();
|
|
|
|
var result = new FilePerformanceResult
|
|
{
|
|
FilePath = filePath,
|
|
PerformanceIssues = new List<PerformanceIssue>(),
|
|
OptimizationOpportunities = new List<OptimizationOpportunity>(),
|
|
CachingOpportunities = new List<CachingOpportunity>(),
|
|
ComplexityIssues = new List<ComplexityIssue>(),
|
|
LoopOptimizations = new List<LoopOptimization>(),
|
|
MemoryOptimizations = new List<MemoryOptimization>(),
|
|
DatabaseOptimizations = new List<DatabaseOptimization>()
|
|
};
|
|
|
|
// Run enabled analyzers
|
|
foreach (var analyzer in analyzers)
|
|
{
|
|
var analysis = await analyzer.AnalyzeAsync(root, filePath, sourceCode);
|
|
|
|
result.PerformanceIssues.AddRange(analysis.PerformanceIssues);
|
|
result.OptimizationOpportunities.AddRange(analysis.OptimizationOpportunities);
|
|
|
|
if (analyzer is ComplexityAnalyzer && analyzeComplexity)
|
|
result.ComplexityIssues.AddRange(analysis.ComplexityIssues);
|
|
|
|
if (analyzer is LoopAnalyzer && checkLoops)
|
|
result.LoopOptimizations.AddRange(analysis.LoopOptimizations);
|
|
|
|
if (analyzer is MemoryAnalyzer && analyzeMemory)
|
|
result.MemoryOptimizations.AddRange(analysis.MemoryOptimizations);
|
|
|
|
if (analyzer is DatabaseAnalyzer && checkDatabase)
|
|
result.DatabaseOptimizations.AddRange(analysis.DatabaseOptimizations);
|
|
|
|
if (analyzer is CachingAnalyzer && suggestCaching)
|
|
result.CachingOpportunities.AddRange(analysis.CachingOpportunities);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private List<IPerformanceAnalyzer> GetAnalyzersForDepth(string depth)
|
|
{
|
|
var analyzers = new List<IPerformanceAnalyzer>();
|
|
|
|
switch (depth.ToLowerInvariant())
|
|
{
|
|
case "basic":
|
|
analyzers.Add(new LoopAnalyzer());
|
|
analyzers.Add(new BasicMemoryAnalyzer());
|
|
break;
|
|
|
|
case "detailed":
|
|
analyzers.Add(new ComplexityAnalyzer());
|
|
analyzers.Add(new LoopAnalyzer());
|
|
analyzers.Add(new MemoryAnalyzer());
|
|
analyzers.Add(new DatabaseAnalyzer());
|
|
break;
|
|
|
|
case "comprehensive":
|
|
analyzers.Add(new ComplexityAnalyzer());
|
|
analyzers.Add(new LoopAnalyzer());
|
|
analyzers.Add(new MemoryAnalyzer());
|
|
analyzers.Add(new DatabaseAnalyzer());
|
|
analyzers.Add(new CachingAnalyzer());
|
|
analyzers.Add(new AsyncAnalyzer());
|
|
analyzers.Add(new CollectionAnalyzer());
|
|
break;
|
|
|
|
default:
|
|
goto case "detailed";
|
|
}
|
|
|
|
return analyzers;
|
|
}
|
|
|
|
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)
|
|
.Where(f => !f.Contains("\\bin\\") && !f.Contains("\\obj\\") &&
|
|
!f.EndsWith(".Designer.cs") && !f.EndsWith(".g.cs")));
|
|
}
|
|
|
|
return files;
|
|
}
|
|
|
|
private int CalculatePerformanceScore(List<PerformanceIssue> issues,
|
|
List<OptimizationOpportunity> opportunities, int totalFiles)
|
|
{
|
|
if (totalFiles == 0) return 100;
|
|
|
|
// Base score
|
|
int score = 100;
|
|
|
|
// Deduct points for issues
|
|
var highSeverityIssues = issues.Count(i => i.Severity == "High");
|
|
var mediumSeverityIssues = issues.Count(i => i.Severity == "Medium");
|
|
var lowSeverityIssues = issues.Count(i => i.Severity == "Low");
|
|
|
|
score -= highSeverityIssues * 15; // -15 points per high severity
|
|
score -= mediumSeverityIssues * 8; // -8 points per medium severity
|
|
score -= lowSeverityIssues * 3; // -3 points per low severity
|
|
|
|
// Additional deduction for high opportunity count
|
|
if (opportunities.Count > totalFiles * 5)
|
|
{
|
|
score -= 20; // Penalty for many missed opportunities
|
|
}
|
|
|
|
return Math.Max(0, Math.Min(100, score));
|
|
}
|
|
|
|
private List<string> GeneratePerformanceRecommendations(
|
|
List<PerformanceIssue> issues,
|
|
List<OptimizationOpportunity> opportunities,
|
|
List<CachingOpportunity> cachingOpportunities)
|
|
{
|
|
var recommendations = new List<string>();
|
|
|
|
if (!issues.Any() && !opportunities.Any())
|
|
{
|
|
recommendations.Add("✅ No significant performance issues detected.");
|
|
return recommendations;
|
|
}
|
|
|
|
recommendations.Add("🚀 Performance Optimization Recommendations:");
|
|
|
|
// High priority issues
|
|
var highPriorityIssues = issues.Where(i => i.Severity == "High").ToList();
|
|
if (highPriorityIssues.Any())
|
|
{
|
|
recommendations.Add($"🔥 HIGH PRIORITY - Address {highPriorityIssues.Count} critical performance issue(s):");
|
|
foreach (var issue in highPriorityIssues.Take(5))
|
|
{
|
|
recommendations.Add($" • {issue.ClassName}.{issue.MethodName}: {issue.Description}");
|
|
}
|
|
}
|
|
|
|
// Memory optimizations
|
|
var memoryIssues = issues.Where(i => i.Category == "Memory").ToList();
|
|
if (memoryIssues.Any())
|
|
{
|
|
recommendations.Add($"💾 Memory Optimization ({memoryIssues.Count} opportunities):");
|
|
recommendations.Add(" • Use StringBuilder for string concatenations");
|
|
recommendations.Add(" • Implement IDisposable for resource management");
|
|
recommendations.Add(" • Consider object pooling for frequently allocated objects");
|
|
}
|
|
|
|
// Loop optimizations
|
|
var loopIssues = issues.Where(i => i.Category == "Loop").ToList();
|
|
if (loopIssues.Any())
|
|
{
|
|
recommendations.Add($"🔄 Loop Optimization ({loopIssues.Count} opportunities):");
|
|
recommendations.Add(" • Cache collection.Count in loop variables");
|
|
recommendations.Add(" • Use for loops instead of foreach where appropriate");
|
|
recommendations.Add(" • Consider LINQ optimizations or alternatives");
|
|
}
|
|
|
|
// Database optimizations
|
|
var dbIssues = issues.Where(i => i.Category == "Database").ToList();
|
|
if (dbIssues.Any())
|
|
{
|
|
recommendations.Add($"🗄️ Database Optimization ({dbIssues.Count} opportunities):");
|
|
recommendations.Add(" • Use async database operations");
|
|
recommendations.Add(" • Implement connection pooling");
|
|
recommendations.Add(" • Consider query optimization and indexing");
|
|
}
|
|
|
|
// Caching opportunities
|
|
if (cachingOpportunities.Any())
|
|
{
|
|
var highBenefitCaching = cachingOpportunities.Where(c => c.EstimatedBenefit == "High").ToList();
|
|
if (highBenefitCaching.Any())
|
|
{
|
|
recommendations.Add($"⚡ High-Impact Caching ({highBenefitCaching.Count} opportunities):");
|
|
foreach (var cache in highBenefitCaching.Take(3))
|
|
{
|
|
recommendations.Add($" • {cache.MethodName}: {cache.Rationale}");
|
|
}
|
|
}
|
|
}
|
|
|
|
// General best practices
|
|
recommendations.Add("📋 General Performance Best Practices:");
|
|
recommendations.Add(" • Use async/await for I/O operations");
|
|
recommendations.Add(" • Minimize allocations in hot paths");
|
|
recommendations.Add(" • Profile your application under realistic load");
|
|
recommendations.Add(" • Consider using Span<T> and Memory<T> for high-performance scenarios");
|
|
|
|
return recommendations;
|
|
}
|
|
|
|
private string CalculateEstimatedGain(List<OptimizationOpportunity> opportunities)
|
|
{
|
|
var highImpact = opportunities.Count(o => o.EstimatedImpact == "High");
|
|
var mediumImpact = opportunities.Count(o => o.EstimatedImpact == "Medium");
|
|
var lowImpact = opportunities.Count(o => o.EstimatedImpact == "Low");
|
|
|
|
if (highImpact > 0)
|
|
return $"Significant (>{highImpact} high-impact optimization{(highImpact > 1 ? "s" : "")})";
|
|
if (mediumImpact > 0)
|
|
return $"Moderate ({mediumImpact} medium-impact optimization{(mediumImpact > 1 ? "s" : "")})";
|
|
if (lowImpact > 0)
|
|
return $"Minor ({lowImpact} low-impact optimization{(lowImpact > 1 ? "s" : "")})";
|
|
|
|
return "Minimal";
|
|
}
|
|
|
|
private List<string> GetPriorityActions(List<PerformanceIssue> issues,
|
|
List<OptimizationOpportunity> opportunities)
|
|
{
|
|
var actions = new List<string>();
|
|
|
|
// Top 3 high severity issues
|
|
var topIssues = issues
|
|
.Where(i => i.Severity == "High")
|
|
.OrderBy(i => i.MethodName)
|
|
.Take(3);
|
|
|
|
foreach (var issue in topIssues)
|
|
{
|
|
actions.Add($"Fix {issue.Category.ToLower()} issue in {issue.ClassName}.{issue.MethodName}");
|
|
}
|
|
|
|
// Top 2 high impact opportunities
|
|
var topOpportunities = opportunities
|
|
.Where(o => o.EstimatedImpact == "High")
|
|
.Take(2);
|
|
|
|
foreach (var opp in topOpportunities)
|
|
{
|
|
actions.Add($"Implement {opp.OptimizationType} in {opp.ClassName}.{opp.MethodName}");
|
|
}
|
|
|
|
if (!actions.Any())
|
|
{
|
|
actions.Add("No critical performance issues identified");
|
|
}
|
|
|
|
return actions;
|
|
}
|
|
|
|
private bool GetBoolParameter(IReadOnlyDictionary<string, object> parameters, string key, bool defaultValue)
|
|
{
|
|
return parameters.TryGetValue(key, out var value) ? Convert.ToBoolean(value) : defaultValue;
|
|
}
|
|
}
|
|
|
|
// Supporting interfaces and classes
|
|
public interface IPerformanceAnalyzer
|
|
{
|
|
Task<PerformanceAnalysisResult> AnalyzeAsync(SyntaxNode root, string filePath, string sourceCode);
|
|
}
|
|
|
|
public class PerformanceAnalysisResult
|
|
{
|
|
public List<PerformanceIssue> PerformanceIssues { get; set; } = new();
|
|
public List<OptimizationOpportunity> OptimizationOpportunities { get; set; } = new();
|
|
public List<CachingOpportunity> CachingOpportunities { get; set; } = new();
|
|
public List<ComplexityIssue> ComplexityIssues { get; set; } = new();
|
|
public List<LoopOptimization> LoopOptimizations { get; set; } = new();
|
|
public List<MemoryOptimization> MemoryOptimizations { get; set; } = new();
|
|
public List<DatabaseOptimization> DatabaseOptimizations { get; set; } = new();
|
|
}
|
|
|
|
public class PerformanceIssue
|
|
{
|
|
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 Category { get; set; } = string.Empty;
|
|
public string Description { get; set; } = string.Empty;
|
|
public string Severity { get; set; } = string.Empty;
|
|
public string RecommendedAction { get; set; } = string.Empty;
|
|
}
|
|
|
|
public class OptimizationOpportunity
|
|
{
|
|
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 OptimizationType { get; set; } = string.Empty;
|
|
public string Description { get; set; } = string.Empty;
|
|
public string EstimatedImpact { get; set; } = string.Empty;
|
|
public string Suggestion { get; set; } = string.Empty;
|
|
}
|
|
|
|
public class ComplexityIssue
|
|
{
|
|
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 AlgorithmicComplexity { get; set; } = string.Empty;
|
|
public string Description { get; set; } = string.Empty;
|
|
public string Severity { get; set; } = string.Empty;
|
|
public string RecommendedAction { get; set; } = string.Empty;
|
|
}
|
|
|
|
public class LoopOptimization
|
|
{
|
|
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 LoopType { get; set; } = string.Empty;
|
|
public string IssueType { get; set; } = string.Empty;
|
|
public string Description { get; set; } = string.Empty;
|
|
public string Severity { get; set; } = string.Empty;
|
|
public string Suggestion { get; set; } = string.Empty;
|
|
}
|
|
|
|
public class MemoryOptimization
|
|
{
|
|
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 IssueType { get; set; } = string.Empty;
|
|
public string Description { get; set; } = string.Empty;
|
|
public string EstimatedImpact { get; set; } = string.Empty;
|
|
public string Suggestion { get; set; } = string.Empty;
|
|
}
|
|
|
|
public class DatabaseOptimization
|
|
{
|
|
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 QueryType { get; set; } = string.Empty;
|
|
public string IssueType { get; set; } = string.Empty;
|
|
public string Description { get; set; } = string.Empty;
|
|
public string Severity { get; set; } = string.Empty;
|
|
public string Suggestion { get; set; } = string.Empty;
|
|
}
|
|
|
|
public class CachingOpportunity
|
|
{
|
|
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 CacheType { get; set; } = string.Empty;
|
|
public string Rationale { get; set; } = string.Empty;
|
|
public string EstimatedBenefit { get; set; } = string.Empty;
|
|
public string Implementation { get; set; } = string.Empty;
|
|
}
|
|
|
|
public class FilePerformanceResult
|
|
{
|
|
public string FilePath { get; set; } = string.Empty;
|
|
public List<PerformanceIssue> PerformanceIssues { get; set; } = new();
|
|
public List<OptimizationOpportunity> OptimizationOpportunities { get; set; } = new();
|
|
public List<CachingOpportunity> CachingOpportunities { get; set; } = new();
|
|
public List<ComplexityIssue> ComplexityIssues { get; set; } = new();
|
|
public List<LoopOptimization> LoopOptimizations { get; set; } = new();
|
|
public List<MemoryOptimization> MemoryOptimizations { get; set; } = new();
|
|
public List<DatabaseOptimization> DatabaseOptimizations { get; set; } = new();
|
|
}
|
|
|
|
// Concrete analyzer implementations
|
|
public class LoopAnalyzer : IPerformanceAnalyzer
|
|
{
|
|
public Task<PerformanceAnalysisResult> AnalyzeAsync(SyntaxNode root, string filePath, string sourceCode)
|
|
{
|
|
var result = new PerformanceAnalysisResult();
|
|
|
|
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>();
|
|
|
|
foreach (var method in methods)
|
|
{
|
|
var className = GetClassName(method);
|
|
var methodName = method.Identifier.ValueText;
|
|
|
|
// Analyze for loops
|
|
var forLoops = method.DescendantNodes().OfType<ForStatementSyntax>();
|
|
foreach (var forLoop in forLoops)
|
|
{
|
|
AnalyzeForLoop(forLoop, className, methodName, filePath, result);
|
|
}
|
|
|
|
// Analyze foreach loops
|
|
var foreachLoops = method.DescendantNodes().OfType<ForEachStatementSyntax>();
|
|
foreach (var foreachLoop in foreachLoops)
|
|
{
|
|
AnalyzeForeachLoop(foreachLoop, className, methodName, filePath, result);
|
|
}
|
|
|
|
// Analyze while loops
|
|
var whileLoops = method.DescendantNodes().OfType<WhileStatementSyntax>();
|
|
foreach (var whileLoop in whileLoops)
|
|
{
|
|
AnalyzeWhileLoop(whileLoop, className, methodName, filePath, result);
|
|
}
|
|
}
|
|
|
|
return Task.FromResult(result);
|
|
}
|
|
|
|
private void AnalyzeForLoop(ForStatementSyntax forLoop, string className, string methodName,
|
|
string filePath, PerformanceAnalysisResult result)
|
|
{
|
|
var lineNumber = forLoop.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
|
|
|
|
// Check for inefficient condition evaluation
|
|
if (forLoop.Condition != null)
|
|
{
|
|
var conditionText = forLoop.Condition.ToString();
|
|
if (conditionText.Contains(".Count") || conditionText.Contains(".Length"))
|
|
{
|
|
result.LoopOptimizations.Add(new LoopOptimization
|
|
{
|
|
MethodName = methodName,
|
|
ClassName = className,
|
|
FilePath = filePath,
|
|
LineNumber = lineNumber,
|
|
LoopType = "for",
|
|
IssueType = "Repeated property access",
|
|
Description = "Loop condition evaluates collection.Count or array.Length on each iteration",
|
|
Severity = "Medium",
|
|
Suggestion = "Cache the collection size in a local variable before the loop"
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
private void AnalyzeForeachLoop(ForEachStatementSyntax foreachLoop, string className, string methodName,
|
|
string filePath, PerformanceAnalysisResult result)
|
|
{
|
|
var lineNumber = foreachLoop.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
|
|
|
|
// Check if foreach is used with indexed access inside
|
|
var body = foreachLoop.Statement;
|
|
if (body != null)
|
|
{
|
|
var elementAccesses = body.DescendantNodes().OfType<ElementAccessExpressionSyntax>();
|
|
if (elementAccesses.Any())
|
|
{
|
|
result.LoopOptimizations.Add(new LoopOptimization
|
|
{
|
|
MethodName = methodName,
|
|
ClassName = className,
|
|
FilePath = filePath,
|
|
LineNumber = lineNumber,
|
|
LoopType = "foreach",
|
|
IssueType = "Indexed access in foreach",
|
|
Description = "Using indexed access inside foreach loop - consider using for loop instead",
|
|
Severity = "Low",
|
|
Suggestion = "Use for loop with index when you need indexed access"
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
private void AnalyzeWhileLoop(WhileStatementSyntax whileLoop, string className, string methodName,
|
|
string filePath, PerformanceAnalysisResult result)
|
|
{
|
|
var lineNumber = whileLoop.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
|
|
|
|
// Basic analysis - could be expanded
|
|
var conditionText = whileLoop.Condition.ToString();
|
|
if (conditionText.Contains("true"))
|
|
{
|
|
result.PerformanceIssues.Add(new PerformanceIssue
|
|
{
|
|
MethodName = methodName,
|
|
ClassName = className,
|
|
FilePath = filePath,
|
|
LineNumber = lineNumber,
|
|
Category = "Loop",
|
|
Description = "Infinite loop detected - ensure proper exit condition",
|
|
Severity = "High",
|
|
RecommendedAction = "Add proper termination condition or use different loop construct"
|
|
});
|
|
}
|
|
}
|
|
|
|
private string GetClassName(SyntaxNode node)
|
|
{
|
|
return node.Ancestors().OfType<ClassDeclarationSyntax>().FirstOrDefault()?.Identifier.ValueText ?? "Unknown";
|
|
}
|
|
}
|
|
|
|
public class MemoryAnalyzer : IPerformanceAnalyzer
|
|
{
|
|
public Task<PerformanceAnalysisResult> AnalyzeAsync(SyntaxNode root, string filePath, string sourceCode)
|
|
{
|
|
var result = new PerformanceAnalysisResult();
|
|
|
|
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>();
|
|
|
|
foreach (var method in methods)
|
|
{
|
|
var className = GetClassName(method);
|
|
var methodName = method.Identifier.ValueText;
|
|
|
|
AnalyzeStringConcatenation(method, className, methodName, filePath, result);
|
|
AnalyzeDisposablePatterns(method, className, methodName, filePath, result);
|
|
AnalyzeCollectionUsage(method, className, methodName, filePath, result);
|
|
}
|
|
|
|
return Task.FromResult(result);
|
|
}
|
|
|
|
private void AnalyzeStringConcatenation(MethodDeclarationSyntax method, string className,
|
|
string methodName, string filePath, PerformanceAnalysisResult result)
|
|
{
|
|
var lineNumber = method.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
|
|
|
|
// Look for string concatenation in loops
|
|
var loops = method.DescendantNodes().Where(n =>
|
|
n is ForStatementSyntax || n is ForEachStatementSyntax || n is WhileStatementSyntax);
|
|
|
|
foreach (var loop in loops)
|
|
{
|
|
var assignments = loop.DescendantNodes().OfType<AssignmentExpressionSyntax>()
|
|
.Where(a => a.OperatorToken.IsKind(SyntaxKind.PlusEqualsToken));
|
|
|
|
foreach (var assignment in assignments)
|
|
{
|
|
if (IsStringType(assignment.Left))
|
|
{
|
|
result.MemoryOptimizations.Add(new MemoryOptimization
|
|
{
|
|
MethodName = methodName,
|
|
ClassName = className,
|
|
FilePath = filePath,
|
|
LineNumber = assignment.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
|
|
IssueType = "String concatenation in loop",
|
|
Description = "String concatenation inside loop creates multiple temporary strings",
|
|
EstimatedImpact = "High",
|
|
Suggestion = "Use StringBuilder for multiple string concatenations"
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void AnalyzeDisposablePatterns(MethodDeclarationSyntax method, string className,
|
|
string methodName, string filePath, PerformanceAnalysisResult result)
|
|
{
|
|
var lineNumber = method.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
|
|
|
|
// Look for new expressions that might create disposable objects
|
|
var objectCreations = method.DescendantNodes().OfType<ObjectCreationExpressionSyntax>();
|
|
|
|
foreach (var creation in objectCreations)
|
|
{
|
|
var typeName = creation.Type.ToString();
|
|
|
|
// Check for common disposable types not in using statements
|
|
if (IsDisposableType(typeName) && !IsInUsingStatement(creation))
|
|
{
|
|
result.MemoryOptimizations.Add(new MemoryOptimization
|
|
{
|
|
MethodName = methodName,
|
|
ClassName = className,
|
|
FilePath = filePath,
|
|
LineNumber = creation.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
|
|
IssueType = "Disposable not in using statement",
|
|
Description = $"Creating {typeName} without proper disposal pattern",
|
|
EstimatedImpact = "Medium",
|
|
Suggestion = "Wrap disposable objects in using statements or implement try-finally"
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
private void AnalyzeCollectionUsage(MethodDeclarationSyntax method, string className,
|
|
string methodName, string filePath, PerformanceAnalysisResult result)
|
|
{
|
|
var lineNumber = method.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
|
|
|
|
// Look for inefficient collection operations
|
|
var invocations = method.DescendantNodes().OfType<InvocationExpressionSyntax>();
|
|
|
|
foreach (var invocation in invocations)
|
|
{
|
|
var memberAccess = invocation.Expression as MemberAccessExpressionSyntax;
|
|
if (memberAccess != null)
|
|
{
|
|
var methodCall = memberAccess.Name.Identifier.ValueText;
|
|
|
|
// Check for inefficient LINQ operations
|
|
if (methodCall == "Count" && HasLinqWhere(invocation))
|
|
{
|
|
result.MemoryOptimizations.Add(new MemoryOptimization
|
|
{
|
|
MethodName = methodName,
|
|
ClassName = className,
|
|
FilePath = filePath,
|
|
LineNumber = invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
|
|
IssueType = "Inefficient LINQ usage",
|
|
Description = "Using Where().Count() instead of Count(predicate)",
|
|
EstimatedImpact = "Low",
|
|
Suggestion = "Use Count(predicate) instead of Where(predicate).Count()"
|
|
});
|
|
}
|
|
|
|
if (methodCall == "Any" && HasLinqWhere(invocation))
|
|
{
|
|
result.MemoryOptimizations.Add(new MemoryOptimization
|
|
{
|
|
MethodName = methodName,
|
|
ClassName = className,
|
|
FilePath = filePath,
|
|
LineNumber = invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
|
|
IssueType = "Inefficient LINQ usage",
|
|
Description = "Using Where().Any() instead of Any(predicate)",
|
|
EstimatedImpact = "Low",
|
|
Suggestion = "Use Any(predicate) instead of Where(predicate).Any()"
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool IsStringType(SyntaxNode node)
|
|
{
|
|
// Simple heuristic - could be improved with semantic analysis
|
|
return node.ToString().Contains("string") || node.ToString().Contains("String");
|
|
}
|
|
|
|
private bool IsDisposableType(string typeName)
|
|
{
|
|
var disposableTypes = new[]
|
|
{
|
|
"FileStream", "StreamReader", "StreamWriter", "HttpClient", "SqlConnection",
|
|
"SqlCommand", "DbConnection", "DbCommand", "BinaryReader", "BinaryWriter",
|
|
"StringReader", "StringWriter", "MemoryStream", "NetworkStream"
|
|
};
|
|
|
|
return disposableTypes.Any(dt => typeName.Contains(dt));
|
|
}
|
|
|
|
private bool IsInUsingStatement(SyntaxNode node)
|
|
{
|
|
return node.Ancestors().OfType<UsingStatementSyntax>().Any();
|
|
}
|
|
|
|
private bool HasLinqWhere(InvocationExpressionSyntax invocation)
|
|
{
|
|
var memberAccess = invocation.Expression as MemberAccessExpressionSyntax;
|
|
if (memberAccess?.Expression is InvocationExpressionSyntax parentInvocation)
|
|
{
|
|
var parentMemberAccess = parentInvocation.Expression as MemberAccessExpressionSyntax;
|
|
return parentMemberAccess?.Name.Identifier.ValueText == "Where";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private string GetClassName(SyntaxNode node)
|
|
{
|
|
return node.Ancestors().OfType<ClassDeclarationSyntax>().FirstOrDefault()?.Identifier.ValueText ?? "Unknown";
|
|
}
|
|
}
|
|
|
|
public class BasicMemoryAnalyzer : MemoryAnalyzer
|
|
{
|
|
// Simplified version for basic analysis
|
|
}
|
|
|
|
public class DatabaseAnalyzer : IPerformanceAnalyzer
|
|
{
|
|
public Task<PerformanceAnalysisResult> AnalyzeAsync(SyntaxNode root, string filePath, string sourceCode)
|
|
{
|
|
var result = new PerformanceAnalysisResult();
|
|
|
|
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>();
|
|
|
|
foreach (var method in methods)
|
|
{
|
|
var className = GetClassName(method);
|
|
var methodName = method.Identifier.ValueText;
|
|
|
|
AnalyzeDatabaseOperations(method, className, methodName, filePath, result);
|
|
AnalyzeAsyncPatterns(method, className, methodName, filePath, result);
|
|
}
|
|
|
|
return Task.FromResult(result);
|
|
}
|
|
|
|
private void AnalyzeDatabaseOperations(MethodDeclarationSyntax method, string className,
|
|
string methodName, string filePath, PerformanceAnalysisResult result)
|
|
{
|
|
var lineNumber = method.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
|
|
|
|
// Look for database-related method calls
|
|
var invocations = method.DescendantNodes().OfType<InvocationExpressionSyntax>();
|
|
|
|
foreach (var invocation in invocations)
|
|
{
|
|
var memberAccess = invocation.Expression as MemberAccessExpressionSyntax;
|
|
if (memberAccess != null)
|
|
{
|
|
var methodCall = memberAccess.Name.Identifier.ValueText;
|
|
|
|
// Check for synchronous database operations
|
|
if (IsDatabaseMethod(methodCall) && !IsAsyncMethod(method))
|
|
{
|
|
result.DatabaseOptimizations.Add(new DatabaseOptimization
|
|
{
|
|
MethodName = methodName,
|
|
ClassName = className,
|
|
FilePath = filePath,
|
|
LineNumber = invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
|
|
QueryType = "Database Operation",
|
|
IssueType = "Synchronous database call",
|
|
Description = $"Synchronous {methodCall} call can block thread",
|
|
Severity = "Medium",
|
|
Suggestion = $"Use {methodCall}Async() and make method async"
|
|
});
|
|
}
|
|
|
|
// Check for potential N+1 queries
|
|
if (IsInLoop(invocation) && IsDatabaseMethod(methodCall))
|
|
{
|
|
result.DatabaseOptimizations.Add(new DatabaseOptimization
|
|
{
|
|
MethodName = methodName,
|
|
ClassName = className,
|
|
FilePath = filePath,
|
|
LineNumber = invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
|
|
QueryType = "Database Operation",
|
|
IssueType = "Potential N+1 query",
|
|
Description = "Database query inside loop may cause N+1 query problem",
|
|
Severity = "High",
|
|
Suggestion = "Consider batching queries or using joins/includes"
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void AnalyzeAsyncPatterns(MethodDeclarationSyntax method, string className,
|
|
string methodName, string filePath, PerformanceAnalysisResult result)
|
|
{
|
|
var lineNumber = method.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
|
|
|
|
// Look for .Result or .Wait() calls on async operations
|
|
var memberAccesses = method.DescendantNodes().OfType<MemberAccessExpressionSyntax>();
|
|
|
|
foreach (var memberAccess in memberAccesses)
|
|
{
|
|
if (memberAccess.Name.Identifier.ValueText == "Result" ||
|
|
memberAccess.Name.Identifier.ValueText == "Wait")
|
|
{
|
|
result.DatabaseOptimizations.Add(new DatabaseOptimization
|
|
{
|
|
MethodName = methodName,
|
|
ClassName = className,
|
|
FilePath = filePath,
|
|
LineNumber = memberAccess.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
|
|
QueryType = "Async Pattern",
|
|
IssueType = "Blocking async operation",
|
|
Description = "Using .Result or .Wait() can cause deadlocks",
|
|
Severity = "High",
|
|
Suggestion = "Use await instead of .Result/.Wait()"
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool IsDatabaseMethod(string methodName)
|
|
{
|
|
var dbMethods = new[]
|
|
{
|
|
"ExecuteScalar", "ExecuteNonQuery", "ExecuteReader", "Query", "QueryFirst",
|
|
"QuerySingle", "Execute", "SaveChanges", "Add", "Update", "Remove", "Find"
|
|
};
|
|
|
|
return dbMethods.Contains(methodName);
|
|
}
|
|
|
|
private bool IsAsyncMethod(MethodDeclarationSyntax method)
|
|
{
|
|
return method.Modifiers.Any(m => m.IsKind(SyntaxKind.AsyncKeyword));
|
|
}
|
|
|
|
private bool IsInLoop(SyntaxNode node)
|
|
{
|
|
return node.Ancestors().Any(a =>
|
|
a is ForStatementSyntax || a is ForEachStatementSyntax || a is WhileStatementSyntax);
|
|
}
|
|
|
|
private string GetClassName(SyntaxNode node)
|
|
{
|
|
return node.Ancestors().OfType<ClassDeclarationSyntax>().FirstOrDefault()?.Identifier.ValueText ?? "Unknown";
|
|
}
|
|
}
|
|
|
|
public class CachingAnalyzer : IPerformanceAnalyzer
|
|
{
|
|
public Task<PerformanceAnalysisResult> AnalyzeAsync(SyntaxNode root, string filePath, string sourceCode)
|
|
{
|
|
var result = new PerformanceAnalysisResult();
|
|
|
|
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>();
|
|
|
|
foreach (var method in methods)
|
|
{
|
|
var className = GetClassName(method);
|
|
var methodName = method.Identifier.ValueText;
|
|
|
|
AnalyzeCachingOpportunities(method, className, methodName, filePath, result);
|
|
}
|
|
|
|
return Task.FromResult(result);
|
|
}
|
|
|
|
private void AnalyzeCachingOpportunities(MethodDeclarationSyntax method, string className,
|
|
string methodName, string filePath, PerformanceAnalysisResult result)
|
|
{
|
|
var lineNumber = method.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
|
|
|
|
// Look for expensive operations that could benefit from caching
|
|
var invocations = method.DescendantNodes().OfType<InvocationExpressionSyntax>();
|
|
|
|
foreach (var invocation in invocations)
|
|
{
|
|
var memberAccess = invocation.Expression as MemberAccessExpressionSyntax;
|
|
if (memberAccess != null)
|
|
{
|
|
var methodCall = memberAccess.Name.Identifier.ValueText;
|
|
|
|
// Database queries
|
|
if (IsDatabaseMethod(methodCall) && HasSimpleParameters(invocation))
|
|
{
|
|
result.CachingOpportunities.Add(new CachingOpportunity
|
|
{
|
|
MethodName = methodName,
|
|
ClassName = className,
|
|
FilePath = filePath,
|
|
LineNumber = invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
|
|
CacheType = "Query Result Cache",
|
|
Rationale = "Database query with deterministic parameters",
|
|
EstimatedBenefit = "High",
|
|
Implementation = "Consider using IMemoryCache or distributed cache"
|
|
});
|
|
}
|
|
|
|
// HTTP calls
|
|
if (IsHttpMethod(methodCall))
|
|
{
|
|
result.CachingOpportunities.Add(new CachingOpportunity
|
|
{
|
|
MethodName = methodName,
|
|
ClassName = className,
|
|
FilePath = filePath,
|
|
LineNumber = invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
|
|
CacheType = "HTTP Response Cache",
|
|
Rationale = "External HTTP call that could be cached",
|
|
EstimatedBenefit = "Medium",
|
|
Implementation = "Implement HTTP response caching with appropriate TTL"
|
|
});
|
|
}
|
|
|
|
// Expensive computations
|
|
if (IsExpensiveComputation(methodCall))
|
|
{
|
|
result.CachingOpportunities.Add(new CachingOpportunity
|
|
{
|
|
MethodName = methodName,
|
|
ClassName = className,
|
|
FilePath = filePath,
|
|
LineNumber = invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
|
|
CacheType = "Computation Cache",
|
|
Rationale = "Expensive computation that could be memoized",
|
|
EstimatedBenefit = "Medium",
|
|
Implementation = "Consider memoization pattern or result caching"
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool IsDatabaseMethod(string methodName)
|
|
{
|
|
return new[] { "Query", "QueryFirst", "QuerySingle", "Find", "Where", "Select" }.Contains(methodName);
|
|
}
|
|
|
|
private bool IsHttpMethod(string methodName)
|
|
{
|
|
return new[] { "GetAsync", "PostAsync", "PutAsync", "DeleteAsync", "SendAsync" }.Contains(methodName);
|
|
}
|
|
|
|
private bool IsExpensiveComputation(string methodName)
|
|
{
|
|
return new[] { "Calculate", "Compute", "Process", "Transform", "Parse", "Serialize" }
|
|
.Any(keyword => methodName.Contains(keyword));
|
|
}
|
|
|
|
private bool HasSimpleParameters(InvocationExpressionSyntax invocation)
|
|
{
|
|
// Simple heuristic: check if parameters are likely to be cacheable
|
|
return invocation.ArgumentList.Arguments.Count <= 3;
|
|
}
|
|
|
|
private string GetClassName(SyntaxNode node)
|
|
{
|
|
return node.Ancestors().OfType<ClassDeclarationSyntax>().FirstOrDefault()?.Identifier.ValueText ?? "Unknown";
|
|
}
|
|
}
|
|
|
|
public class ComplexityAnalyzer : IPerformanceAnalyzer
|
|
{
|
|
public Task<PerformanceAnalysisResult> AnalyzeAsync(SyntaxNode root, string filePath, string sourceCode)
|
|
{
|
|
var result = new PerformanceAnalysisResult();
|
|
|
|
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>();
|
|
|
|
foreach (var method in methods)
|
|
{
|
|
var className = GetClassName(method);
|
|
var methodName = method.Identifier.ValueText;
|
|
|
|
AnalyzeAlgorithmicComplexity(method, className, methodName, filePath, result);
|
|
}
|
|
|
|
return Task.FromResult(result);
|
|
}
|
|
|
|
private void AnalyzeAlgorithmicComplexity(MethodDeclarationSyntax method, string className,
|
|
string methodName, string filePath, PerformanceAnalysisResult result)
|
|
{
|
|
var lineNumber = method.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
|
|
var complexity = EstimateComplexity(method);
|
|
|
|
if (complexity.StartsWith("O(n²)") || complexity.StartsWith("O(n³)"))
|
|
{
|
|
result.ComplexityIssues.Add(new ComplexityIssue
|
|
{
|
|
MethodName = methodName,
|
|
ClassName = className,
|
|
FilePath = filePath,
|
|
LineNumber = lineNumber,
|
|
AlgorithmicComplexity = complexity,
|
|
Description = $"Method has {complexity} algorithmic complexity",
|
|
Severity = complexity.StartsWith("O(n³)") ? "High" : "Medium",
|
|
RecommendedAction = "Consider optimizing algorithm or using more efficient data structures"
|
|
});
|
|
}
|
|
}
|
|
|
|
private string EstimateComplexity(MethodDeclarationSyntax method)
|
|
{
|
|
var nestedLoopDepth = CalculateNestedLoopDepth(method);
|
|
|
|
return nestedLoopDepth switch
|
|
{
|
|
0 => "O(1)",
|
|
1 => "O(n)",
|
|
2 => "O(n²)",
|
|
3 => "O(n³)",
|
|
_ => $"O(n^{nestedLoopDepth})"
|
|
};
|
|
}
|
|
|
|
private int CalculateNestedLoopDepth(SyntaxNode node)
|
|
{
|
|
int maxDepth = 0;
|
|
CalculateNestedDepth(node, 0, ref maxDepth);
|
|
return maxDepth;
|
|
}
|
|
|
|
private void CalculateNestedDepth(SyntaxNode node, int currentDepth, ref int maxDepth)
|
|
{
|
|
if (node is ForStatementSyntax || node is ForEachStatementSyntax || node is WhileStatementSyntax)
|
|
{
|
|
currentDepth++;
|
|
maxDepth = Math.Max(maxDepth, currentDepth);
|
|
}
|
|
|
|
foreach (var child in node.ChildNodes())
|
|
{
|
|
CalculateNestedDepth(child, currentDepth, ref maxDepth);
|
|
}
|
|
}
|
|
|
|
private string GetClassName(SyntaxNode node)
|
|
{
|
|
return node.Ancestors().OfType<ClassDeclarationSyntax>().FirstOrDefault()?.Identifier.ValueText ?? "Unknown";
|
|
}
|
|
}
|
|
|
|
public class AsyncAnalyzer : IPerformanceAnalyzer
|
|
{
|
|
public Task<PerformanceAnalysisResult> AnalyzeAsync(SyntaxNode root, string filePath, string sourceCode)
|
|
{
|
|
var result = new PerformanceAnalysisResult();
|
|
|
|
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>();
|
|
|
|
foreach (var method in methods)
|
|
{
|
|
var className = GetClassName(method);
|
|
var methodName = method.Identifier.ValueText;
|
|
|
|
AnalyzeAsyncUsage(method, className, methodName, filePath, result);
|
|
}
|
|
|
|
return Task.FromResult(result);
|
|
}
|
|
|
|
private void AnalyzeAsyncUsage(MethodDeclarationSyntax method, string className,
|
|
string methodName, string filePath, PerformanceAnalysisResult result)
|
|
{
|
|
var lineNumber = method.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
|
|
|
|
// Check for missing async/await patterns
|
|
var awaitExpressions = method.DescendantNodes().OfType<AwaitExpressionSyntax>();
|
|
var isAsync = method.Modifiers.Any(m => m.IsKind(SyntaxKind.AsyncKeyword));
|
|
|
|
if (awaitExpressions.Any() && !isAsync)
|
|
{
|
|
result.PerformanceIssues.Add(new PerformanceIssue
|
|
{
|
|
MethodName = methodName,
|
|
ClassName = className,
|
|
FilePath = filePath,
|
|
LineNumber = lineNumber,
|
|
Category = "Async",
|
|
Description = "Method uses await but is not marked as async",
|
|
Severity = "High",
|
|
RecommendedAction = "Add async modifier to method signature"
|
|
});
|
|
}
|
|
}
|
|
|
|
private string GetClassName(SyntaxNode node)
|
|
{
|
|
return node.Ancestors().OfType<ClassDeclarationSyntax>().FirstOrDefault()?.Identifier.ValueText ?? "Unknown";
|
|
}
|
|
}
|
|
|
|
public class CollectionAnalyzer : IPerformanceAnalyzer
|
|
{
|
|
public Task<PerformanceAnalysisResult> AnalyzeAsync(SyntaxNode root, string filePath, string sourceCode)
|
|
{
|
|
var result = new PerformanceAnalysisResult();
|
|
|
|
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>();
|
|
|
|
foreach (var method in methods)
|
|
{
|
|
var className = GetClassName(method);
|
|
var methodName = method.Identifier.ValueText;
|
|
|
|
AnalyzeCollectionUsage(method, className, methodName, filePath, result);
|
|
}
|
|
|
|
return Task.FromResult(result);
|
|
}
|
|
|
|
private void AnalyzeCollectionUsage(MethodDeclarationSyntax method, string className,
|
|
string methodName, string filePath, PerformanceAnalysisResult result)
|
|
{
|
|
var lineNumber = method.GetLocation().GetLineSpan().StartLinePosition.Line + 1;
|
|
|
|
// Look for inefficient collection operations
|
|
var invocations = method.DescendantNodes().OfType<InvocationExpressionSyntax>();
|
|
|
|
foreach (var invocation in invocations)
|
|
{
|
|
var memberAccess = invocation.Expression as MemberAccessExpressionSyntax;
|
|
if (memberAccess != null)
|
|
{
|
|
var methodCall = memberAccess.Name.Identifier.ValueText;
|
|
|
|
// Multiple enumeration
|
|
if (methodCall == "ToList" && IsChainedWithLinq(invocation))
|
|
{
|
|
result.OptimizationOpportunities.Add(new OptimizationOpportunity
|
|
{
|
|
MethodName = methodName,
|
|
ClassName = className,
|
|
FilePath = filePath,
|
|
LineNumber = invocation.GetLocation().GetLineSpan().StartLinePosition.Line + 1,
|
|
OptimizationType = "Collection optimization",
|
|
Description = "Multiple LINQ operations - consider optimizing query",
|
|
EstimatedImpact = "Medium",
|
|
Suggestion = "Combine LINQ operations or use more efficient approach"
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool IsChainedWithLinq(InvocationExpressionSyntax invocation)
|
|
{
|
|
var memberAccess = invocation.Expression as MemberAccessExpressionSyntax;
|
|
return memberAccess?.Expression is InvocationExpressionSyntax;
|
|
}
|
|
|
|
private string GetClassName(SyntaxNode node)
|
|
{
|
|
return node.Ancestors().OfType<ClassDeclarationSyntax>().FirstOrDefault()?.Identifier.ValueText ?? "Unknown";
|
|
}
|
|
}
|
|
} |