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.