< Summary

Information
Class: NexusLabs.Needlr.AgentFramework.Analyzers.AgentTopologyCodeFixProvider
Assembly: NexusLabs.Needlr.AgentFramework.Analyzers
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework.Analyzers/AgentTopologyCodeFixProvider.cs
Line coverage
85%
Covered lines: 70
Uncovered lines: 12
Coverable lines: 82
Total lines: 147
Line coverage: 85.3%
Branch coverage
58%
Covered branches: 20
Total branches: 34
Branch coverage: 58.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_FixableDiagnosticIds()100%11100%
GetFixAllProvider()100%11100%
RegisterCodeFixesAsync()87.5%8888.88%
RegisterSourceFix(...)50%4490.9%
RegisterTargetFixAsync()50%111080.95%
AddAttributeToDocumentAsync()50%2283.33%
AddAttributeToTargetAsync()50%9873.33%
PrependNeedlrAiAgent(...)50%2292.3%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework.Analyzers/AgentTopologyCodeFixProvider.cs

#LineLine coverage
 1using System.Collections.Immutable;
 2using System.Composition;
 3using System.Linq;
 4using System.Threading;
 5using System.Threading.Tasks;
 6
 7using Microsoft.CodeAnalysis;
 8using Microsoft.CodeAnalysis.CodeActions;
 9using Microsoft.CodeAnalysis.CodeFixes;
 10using Microsoft.CodeAnalysis.CSharp;
 11using Microsoft.CodeAnalysis.CSharp.Syntax;
 12
 13namespace NexusLabs.Needlr.AgentFramework.Analyzers;
 14
 15[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AgentTopologyCodeFixProvider))]
 16[Shared]
 17public sealed class AgentTopologyCodeFixProvider : CodeFixProvider
 18{
 19    private const string Title = "Add [NeedlrAiAgent]";
 20
 21    public override ImmutableArray<string> FixableDiagnosticIds =>
 86222        ImmutableArray.Create(
 86223            MafDiagnosticIds.HandoffsToTargetNotNeedlrAgent,
 86224            MafDiagnosticIds.HandoffsToSourceNotNeedlrAgent);
 25
 826    public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
 27
 28    public override async Task RegisterCodeFixesAsync(CodeFixContext context)
 29    {
 1430        var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
 1431        if (root is null)
 032            return;
 33
 5634        foreach (var diagnostic in context.Diagnostics)
 35        {
 1436            if (diagnostic.Id == MafDiagnosticIds.HandoffsToSourceNotNeedlrAgent)
 737                RegisterSourceFix(context, root, diagnostic);
 738            else if (diagnostic.Id == MafDiagnosticIds.HandoffsToTargetNotNeedlrAgent)
 739                await RegisterTargetFixAsync(context, root, diagnostic).ConfigureAwait(false);
 40        }
 1441    }
 42
 43    private static void RegisterSourceFix(CodeFixContext context, SyntaxNode root, Diagnostic diagnostic)
 44    {
 745        var token = root.FindToken(diagnostic.Location.SourceSpan.Start);
 746        var classDeclaration = token.Parent?.AncestorsAndSelf().OfType<ClassDeclarationSyntax>().FirstOrDefault();
 747        if (classDeclaration is null)
 048            return;
 49
 750        context.RegisterCodeFix(
 751            CodeAction.Create(
 752                title: Title,
 453                createChangedDocument: ct => AddAttributeToDocumentAsync(context.Document, classDeclaration, ct),
 754                equivalenceKey: Title + MafDiagnosticIds.HandoffsToSourceNotNeedlrAgent),
 755            diagnostic);
 756    }
 57
 58    private static async Task RegisterTargetFixAsync(CodeFixContext context, SyntaxNode root, Diagnostic diagnostic)
 59    {
 760        var node = root.FindNode(diagnostic.Location.SourceSpan);
 761        var attributeSyntax = node.AncestorsAndSelf().OfType<AttributeSyntax>().FirstOrDefault();
 762        if (attributeSyntax is null)
 063            return;
 64
 765        var typeOfExpr = attributeSyntax.ArgumentList?.Arguments
 766            .Select(a => a.Expression)
 767            .OfType<TypeOfExpressionSyntax>()
 768            .FirstOrDefault();
 769        if (typeOfExpr is null)
 070            return;
 71
 772        var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false
 773        if (semanticModel is null)
 074            return;
 75
 776        var targetType = semanticModel.GetTypeInfo(typeOfExpr.Type, context.CancellationToken).Type as INamedTypeSymbol;
 777        if (targetType is null)
 078            return;
 79
 780        context.RegisterCodeFix(
 781            CodeAction.Create(
 782                title: Title,
 483                createChangedSolution: ct => AddAttributeToTargetAsync(
 484                    context.Document.Project.Solution, targetType, ct),
 785                equivalenceKey: Title + MafDiagnosticIds.HandoffsToTargetNotNeedlrAgent),
 786            diagnostic);
 787    }
 88
 89    private static async Task<Document> AddAttributeToDocumentAsync(
 90        Document document,
 91        ClassDeclarationSyntax classDeclaration,
 92        CancellationToken ct)
 93    {
 894        var root = await document.GetSyntaxRootAsync(ct).ConfigureAwait(false);
 895        if (root is null)
 096            return document;
 97
 898        var updated = PrependNeedlrAiAgent(classDeclaration);
 899        return document.WithSyntaxRoot(root.ReplaceNode(classDeclaration, updated));
 8100    }
 101
 102    private static async Task<Solution> AddAttributeToTargetAsync(
 103        Solution solution,
 104        INamedTypeSymbol targetType,
 105        CancellationToken ct)
 106    {
 4107        var syntaxRef = targetType.DeclaringSyntaxReferences.FirstOrDefault();
 4108        if (syntaxRef is null)
 0109            return solution;
 110
 4111        var syntax = await syntaxRef.GetSyntaxAsync(ct).ConfigureAwait(false);
 4112        if (syntax is not ClassDeclarationSyntax classDeclaration)
 0113            return solution;
 114
 4115        var documentId = solution.GetDocumentId(syntaxRef.SyntaxTree);
 4116        if (documentId is null)
 0117            return solution;
 118
 4119        var document = solution.GetDocument(documentId);
 4120        if (document is null)
 0121            return solution;
 122
 4123        var updated = await AddAttributeToDocumentAsync(document, classDeclaration, ct).ConfigureAwait(false);
 4124        return updated.Project.Solution;
 4125    }
 126
 127    private static ClassDeclarationSyntax PrependNeedlrAiAgent(ClassDeclarationSyntax classDeclaration)
 128    {
 8129        var attribute = SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("NeedlrAiAgent"));
 8130        var firstToken = classDeclaration.GetFirstToken();
 8131        var leadingTrivia = firstToken.LeadingTrivia;
 132
 16133        var endOfLine = leadingTrivia.FirstOrDefault(t => t.IsKind(SyntaxKind.EndOfLineTrivia));
 8134        if (endOfLine == default)
 0135            endOfLine = SyntaxFactory.LineFeed;
 136
 8137        var attrList = SyntaxFactory.AttributeList(SyntaxFactory.SingletonSeparatedList(attribute))
 8138            .WithLeadingTrivia(leadingTrivia)
 8139            .WithTrailingTrivia(SyntaxFactory.TriviaList(endOfLine));
 140
 8141        var stripped = classDeclaration.ReplaceToken(
 8142            firstToken,
 8143            firstToken.WithLeadingTrivia(SyntaxFactory.TriviaList()));
 144
 8145        return stripped.WithAttributeLists(stripped.AttributeLists.Insert(0, attrList));
 146    }
 147}