Roslyn + LLM ile .NET'te Otomatik Kod Analizi ve Refactoring
Roslyn (.NET Compiler Platform), C# ve VB.NET kaynak kodunu parse edip AST (Abstract Syntax Tree) olarak analiz etmenizi sağlar. Bu güçlü API'yi LLM ile birleştirdiğinizde, yalnızca sözdizimi hatalarını değil; kod kalitesini, mimari sorunları ve iyileştirme fırsatlarını da otomatik olarak tespit edebilirsiniz.
Kurulum
dotnet add package Microsoft.CodeAnalysis.CSharp
dotnet add package Microsoft.CodeAnalysis.Workspaces.MSBuild
dotnet add package OpenAI
Temel Roslyn Analizi
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
public class CodeAnalyzer
{
public async Task<CodeMetrics> AnalyzeFileAsync(string filePath)
{
var code = await File.ReadAllTextAsync(filePath);
var tree = CSharpSyntaxTree.ParseText(code);
var root = await tree.GetRootAsync();
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>().ToList();
var classes = root.DescendantNodes().OfType<ClassDeclarationSyntax>().ToList();
return new CodeMetrics
{
MethodCount = methods.Count,
ClassCount = classes.Count,
LinesOfCode = code.Split('\n').Length,
LongMethods = FindLongMethods(methods, maxLines: 30),
ComplexMethods = FindComplexMethods(methods, maxComplexity: 10),
MissingXmlDocs = FindMissingDocumentation(methods)
};
}
private List<string> FindLongMethods(
IEnumerable<MethodDeclarationSyntax> methods, int maxLines)
{
return methods
.Where(m =>
{
var span = m.GetLocation().GetLineSpan();
return span.EndLinePosition.Line - span.StartLinePosition.Line > maxLines;
})
.Select(m => m.Identifier.Text)
.ToList();
}
private int CalculateCyclomaticComplexity(MethodDeclarationSyntax method)
{
int complexity = 1;
complexity += method.DescendantNodes().Count(n => n is
IfStatementSyntax or
ForStatementSyntax or
ForEachStatementSyntax or
WhileStatementSyntax or
CasePatternSwitchLabelSyntax or
ConditionalExpressionSyntax or
CatchClauseSyntax);
return complexity;
}
}
LLM ile Kod İnceleme
public class LlmCodeReviewer
{
private readonly ChatClient _chatClient;
public async Task<CodeReviewResult> ReviewMethodAsync(
MethodDeclarationSyntax method, string fullContext)
{
var methodCode = method.ToFullString();
var prompt = $"""
Aşağıdaki C# metodunu incele ve şunları değerlendir:
1. SOLID prensiplerinin ihlalleri
2. Performans sorunları (N+1, gereksiz allocation)
3. Güvenlik açıkları (SQL injection, XSS, vb.)
4. Okunabilirlik ve bakım kolaylığı
5. Test edilebilirlik
KOD:
`csharp
{methodCode}
`
BAĞLAM (sınıf adı, namespace, bağımlılıklar):
{fullContext}
JSON formatında yanıtla:
{{
"severity": "low|medium|high",
"issues": [
{{ "type": "...", "description": "...", "line": 0, "suggestion": "..." }}
],
"refactoredCode": "..."
}}
""";
var result = await _chatClient.CompleteChatAsync(
[new UserChatMessage(prompt)],
new ChatCompletionOptions
{
ResponseFormat = ChatResponseFormat.JsonObject,
MaxOutputTokenCount = 2000
});
return JsonSerializer.Deserialize<CodeReviewResult>(
result.Value.Content[0].Text)!;
}
}
Workspace ile Çok Dosyalı Analiz
public class SolutionAnalyzer
{
public async Task AnalyzeSolutionAsync(string solutionPath)
{
MSBuildLocator.RegisterDefaults();
using var workspace = MSBuildWorkspace.Create();
var solution = await workspace.OpenSolutionAsync(solutionPath);
foreach (var project in solution.Projects)
{
Console.WriteLine($"Proje: {project.Name}");
var compilation = await project.GetCompilationAsync();
foreach (var document in project.Documents.Where(d => d.Name.EndsWith(".cs")))
{
var semanticModel = await document.GetSemanticModelAsync();
var root = await document.GetSyntaxRootAsync();
// Kullanılmayan using direktiflerini bul
var unusedUsings = FindUnusedUsings(root!, semanticModel!);
// Async/await sorunlarını bul
var asyncIssues = FindAsyncAntipatterns(root!);
if (unusedUsings.Any() || asyncIssues.Any())
Console.WriteLine($" {document.Name}: {unusedUsings.Count} gereksiz using, {asyncIssues.Count} async sorunu");
}
}
}
private List<string> FindAsyncAntipatterns(SyntaxNode root)
{
var issues = new List<string>();
// .Result veya .Wait() kullanımı — deadlock riski
var blockingCalls = root.DescendantNodes()
.OfType<MemberAccessExpressionSyntax>()
.Where(m => m.Name.Identifier.Text is "Result" or "Wait");
foreach (var call in blockingCalls)
issues.Add($"Blocking async call: {call}");
// async void (event handler dışında)
var asyncVoidMethods = root.DescendantNodes()
.OfType<MethodDeclarationSyntax>()
.Where(m => m.Modifiers.Any(SyntaxKind.AsyncKeyword) &&
m.ReturnType.ToString() == "void");
foreach (var method in asyncVoidMethods)
issues.Add($"async void method: {method.Identifier.Text}");
return issues;
}
}
Otomatik Refactoring Uygulaması
public class AutoRefactoring
{
public async Task<string> RefactorMethodAsync(
string sourceCode, string methodName)
{
var tree = CSharpSyntaxTree.ParseText(sourceCode);
var root = await tree.GetRootAsync();
var method = root.DescendantNodes()
.OfType<MethodDeclarationSyntax>()
.FirstOrDefault(m => m.Identifier.Text == methodName);
if (method == null) return sourceCode;
// LLM'den refactored kod al
var review = await _reviewer.ReviewMethodAsync(method, "");
if (string.IsNullOrEmpty(review.RefactoredCode))
return sourceCode;
// Roslyn ile kodu değiştir
var newMethodTree = CSharpSyntaxTree.ParseText(review.RefactoredCode);
var newMethod = (await newMethodTree.GetRootAsync())
.DescendantNodes()
.OfType<MethodDeclarationSyntax>()
.First();
var newRoot = root.ReplaceNode(method, newMethod);
return newRoot.ToFullString();
}
}
CI/CD Entegrasyonu
# GitHub Actions workflow
# .github/workflows/code-review.yml
# - name: AI Code Review
# run: dotnet run --project tools/CodeReviewer -- --path src/ --fail-on high
// CLI aracı
public static async Task<int> Main(string[] args)
{
var path = args[0]; // --path
var failOn = args.Length > 1 ? args[1] : "high"; // --fail-on
var analyzer = new CodeAnalyzer();
var reviewer = new LlmCodeReviewer(/* ... */);
var files = Directory.GetFiles(path, "*.cs", SearchOption.AllDirectories);
bool hasHighSeverityIssues = false;
foreach (var file in files)
{
var metrics = await analyzer.AnalyzeFileAsync(file);
foreach (var method in metrics.ComplexMethods)
{
var review = await reviewer.ReviewMethodAsync(/* ... */);
if (review.Severity == failOn)
hasHighSeverityIssues = true;
}
}
return hasHighSeverityIssues ? 1 : 0;
}
Sonuç
Roslyn ve LLM'i birleştirmek, kod kalite araçlarına yeni bir boyut katar. Roslyn'in yapısal analizi, LLM'in anlam ve bağlam anlayışıyla birleşince ne sözdizimi ağaçlarının göremediği mimari sorunları ne de kelime tabanlı araçların anlayamadığı nüansları kaçırırsınız. Bu yaklaşımı CI/CD pipeline'ınıza entegre ederek kodun production'a gitmeden önce incelenmesini sağlayabilirsiniz.