< Summary

Information
Class: NexusLabs.Needlr.AgentFramework.Analyzers.TerminationConditionAnalyzer
Assembly: NexusLabs.Needlr.AgentFramework.Analyzers
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework.Analyzers/TerminationConditionAnalyzer.cs
Line coverage
98%
Covered lines: 68
Uncovered lines: 1
Coverable lines: 69
Total lines: 157
Line coverage: 98.5%
Branch coverage
88%
Covered branches: 44
Total branches: 50
Branch coverage: 88%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_SupportedDiagnostics()100%11100%
Initialize(...)100%11100%
AnalyzeType(...)97.22%3636100%
TryGetConditionType(...)50%6685.71%
ImplementsInterface(...)100%44100%
GetAttributeLocation(...)50%44100%

File(s)

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

#LineLine coverage
 1using System.Collections.Immutable;
 2
 3using Microsoft.CodeAnalysis;
 4using Microsoft.CodeAnalysis.Diagnostics;
 5
 6namespace NexusLabs.Needlr.AgentFramework.Analyzers;
 7
 8/// <summary>
 9/// Analyzer that validates termination condition declarations on agent classes.
 10/// </summary>
 11/// <remarks>
 12/// <b>NDLRMAF009</b> (Warning): <c>[WorkflowRunTerminationCondition]</c> is declared on a class
 13/// that is not also decorated with <c>[NeedlrAiAgent]</c>.<br/>
 14/// <b>NDLRMAF010</b> (Error): The <c>conditionType</c> passed to
 15/// <c>[WorkflowRunTerminationCondition]</c> or <c>[AgentTerminationCondition]</c> does not
 16/// implement <c>IWorkflowTerminationCondition</c>.<br/>
 17/// <b>NDLRMAF011</b> (Info): <c>[WorkflowRunTerminationCondition]</c> is declared on a
 18/// <c>[AgentGroupChatMember]</c>; prefer <c>[AgentTerminationCondition]</c> for group chats.
 19/// </remarks>
 20[DiagnosticAnalyzer(LanguageNames.CSharp)]
 21public sealed class TerminationConditionAnalyzer : DiagnosticAnalyzer
 22{
 23    private const string NeedlrAiAgentAttributeName = "NexusLabs.Needlr.AgentFramework.NeedlrAiAgentAttribute";
 24    private const string AgentGroupChatMemberAttributeName = "NexusLabs.Needlr.AgentFramework.AgentGroupChatMemberAttrib
 25    private const string WorkflowRunTerminationConditionAttributeName = "NexusLabs.Needlr.AgentFramework.WorkflowRunTerm
 26    private const string AgentTerminationConditionAttributeName = "NexusLabs.Needlr.AgentFramework.AgentTerminationCondi
 27    private const string IWorkflowTerminationConditionName = "NexusLabs.Needlr.AgentFramework.IWorkflowTerminationCondit
 28
 29    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
 44830        ImmutableArray.Create(
 44831            MafDiagnosticDescriptors.WorkflowRunTerminationConditionOnNonAgent,
 44832            MafDiagnosticDescriptors.TerminationConditionTypeInvalid,
 44833            MafDiagnosticDescriptors.PreferAgentTerminationConditionForGroupChat);
 34
 35    public override void Initialize(AnalysisContext context)
 36    {
 3437        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
 3438        context.EnableConcurrentExecution();
 39
 3440        context.RegisterSymbolAction(AnalyzeType, SymbolKind.NamedType);
 3441    }
 42
 43    private static void AnalyzeType(SymbolAnalysisContext context)
 44    {
 24245        var typeSymbol = (INamedTypeSymbol)context.Symbol;
 46
 24247        bool isAgent = false;
 24248        bool isGroupChatMember = false;
 24249        var workflowRunConditionAttrs = new List<AttributeData>();
 24250        var agentTerminationConditionAttrs = new List<AttributeData>();
 51
 97052        foreach (var attr in typeSymbol.GetAttributes())
 53        {
 24354            var attrName = attr.AttributeClass?.ToDisplayString();
 55
 24356            if (attrName == NeedlrAiAgentAttributeName)
 2457                isAgent = true;
 21958            else if (attrName == AgentGroupChatMemberAttributeName)
 1659                isGroupChatMember = true;
 20360            else if (attrName == WorkflowRunTerminationConditionAttributeName)
 2361                workflowRunConditionAttrs.Add(attr);
 18062            else if (attrName == AgentTerminationConditionAttributeName)
 463                agentTerminationConditionAttrs.Add(attr);
 64        }
 65
 24266        if (workflowRunConditionAttrs.Count == 0 && agentTerminationConditionAttrs.Count == 0)
 22167            return;
 68
 2169        var terminationInterface = context.Compilation.GetTypeByMetadataName(IWorkflowTerminationConditionName);
 70
 8871        foreach (var attr in workflowRunConditionAttrs)
 72        {
 2373            var attrLocation = GetAttributeLocation(attr, typeSymbol);
 74
 75            // NDLRMAF009: [WorkflowRunTerminationCondition] on a non-agent class
 2376            if (!isAgent)
 77            {
 878                context.ReportDiagnostic(Diagnostic.Create(
 879                    MafDiagnosticDescriptors.WorkflowRunTerminationConditionOnNonAgent,
 880                    attrLocation,
 881                    typeSymbol.Name));
 82            }
 83
 84            // NDLRMAF010: conditionType doesn't implement IWorkflowTerminationCondition
 2385            if (TryGetConditionType(attr, out var conditionType) && terminationInterface is not null)
 86            {
 2387                if (!ImplementsInterface(conditionType!, terminationInterface))
 88                {
 889                    context.ReportDiagnostic(Diagnostic.Create(
 890                        MafDiagnosticDescriptors.TerminationConditionTypeInvalid,
 891                        attrLocation,
 892                        conditionType!.Name,
 893                        typeSymbol.Name));
 94                }
 95            }
 96
 97            // NDLRMAF011: [WorkflowRunTerminationCondition] on [AgentGroupChatMember]
 2398            if (isGroupChatMember)
 99            {
 6100                context.ReportDiagnostic(Diagnostic.Create(
 6101                    MafDiagnosticDescriptors.PreferAgentTerminationConditionForGroupChat,
 6102                    attrLocation,
 6103                    typeSymbol.Name));
 104            }
 105        }
 106
 50107        foreach (var attr in agentTerminationConditionAttrs)
 108        {
 109            // NDLRMAF010: conditionType doesn't implement IWorkflowTerminationCondition
 4110            if (TryGetConditionType(attr, out var conditionType) && terminationInterface is not null)
 111            {
 4112                if (!ImplementsInterface(conditionType!, terminationInterface))
 113                {
 2114                    var attrLocation = GetAttributeLocation(attr, typeSymbol);
 2115                    context.ReportDiagnostic(Diagnostic.Create(
 2116                        MafDiagnosticDescriptors.TerminationConditionTypeInvalid,
 2117                        attrLocation,
 2118                        conditionType!.Name,
 2119                        typeSymbol.Name));
 120                }
 121            }
 122        }
 21123    }
 124
 125    private static bool TryGetConditionType(AttributeData attr, out INamedTypeSymbol? conditionType)
 126    {
 27127        conditionType = null;
 128
 27129        if (attr.ConstructorArguments.Length >= 1
 27130            && attr.ConstructorArguments[0].Kind == TypedConstantKind.Type
 27131            && attr.ConstructorArguments[0].Value is INamedTypeSymbol namedType)
 132        {
 27133            conditionType = namedType;
 27134            return true;
 135        }
 136
 0137        return false;
 138    }
 139
 140    private static bool ImplementsInterface(INamedTypeSymbol type, INamedTypeSymbol interfaceSymbol)
 141    {
 71142        foreach (var iface in type.AllInterfaces)
 143        {
 17144            if (SymbolEqualityComparer.Default.Equals(iface, interfaceSymbol))
 17145                return true;
 146        }
 147
 10148        return false;
 149    }
 150
 151    private static Location GetAttributeLocation(AttributeData attr, INamedTypeSymbol fallback)
 152    {
 25153        return attr.ApplicationSyntaxReference?.SyntaxTree is { } tree
 25154            ? Location.Create(tree, attr.ApplicationSyntaxReference.Span)
 25155            : fallback.Locations[0];
 156    }
 157}