< Summary

Information
Class: NexusLabs.Needlr.AgentFramework.Analyzers.AgentOrphanAnalyzer
Assembly: NexusLabs.Needlr.AgentFramework.Analyzers
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework.Analyzers/AgentOrphanAnalyzer.cs
Line coverage
100%
Covered lines: 69
Uncovered lines: 0
Coverable lines: 69
Total lines: 99
Line coverage: 100%
Branch coverage
90%
Covered branches: 27
Total branches: 30
Branch coverage: 90%
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(...)90%3030100%

File(s)

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

#LineLine coverage
 1using System.Collections.Concurrent;
 2using System.Collections.Immutable;
 3
 4using Microsoft.CodeAnalysis;
 5using Microsoft.CodeAnalysis.Diagnostics;
 6
 7namespace NexusLabs.Needlr.AgentFramework.Analyzers;
 8
 9/// <summary>
 10/// Analyzer that detects agent types that participate in no topology declaration.
 11/// </summary>
 12/// <remarks>
 13/// <b>NDLRMAF008</b> (Info): A class decorated with <c>[NeedlrAiAgent]</c> is not referenced in any
 14/// topology attribute (<c>[AgentHandoffsTo]</c>, <c>[AgentGroupChatMember]</c>, or
 15/// <c>[AgentSequenceMember]</c>). This may indicate an orphaned or work-in-progress agent.
 16/// </remarks>
 17[DiagnosticAnalyzer(LanguageNames.CSharp)]
 18public sealed class AgentOrphanAnalyzer : DiagnosticAnalyzer
 19{
 20    private const string NeedlrAiAgentAttributeName = "NexusLabs.Needlr.AgentFramework.NeedlrAiAgentAttribute";
 21    private const string AgentHandoffsToAttributeName = "NexusLabs.Needlr.AgentFramework.AgentHandoffsToAttribute";
 22    private const string AgentGroupChatMemberAttributeName = "NexusLabs.Needlr.AgentFramework.AgentGroupChatMemberAttrib
 23    private const string AgentSequenceMemberAttributeName = "NexusLabs.Needlr.AgentFramework.AgentSequenceMemberAttribut
 24
 25    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
 16526        ImmutableArray.Create(MafDiagnosticDescriptors.OrphanAgent);
 27
 28    public override void Initialize(AnalysisContext context)
 29    {
 1930        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
 1931        context.EnableConcurrentExecution();
 32
 1933        context.RegisterCompilationStartAction(compilationContext =>
 1934        {
 1135            var agents = new ConcurrentDictionary<string, (INamedTypeSymbol Symbol, Location Location)>(StringComparer.O
 1136            var topologyParticipants = new ConcurrentBag<string>();
 1937
 1138            compilationContext.RegisterSymbolAction(symbolContext =>
 1139            {
 12040                var typeSymbol = (INamedTypeSymbol)symbolContext.Symbol;
 12041                var fqn = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 1142
 12043                Location? agentAttrLocation = null;
 12044                bool inTopology = false;
 1145
 47246                foreach (var attr in typeSymbol.GetAttributes())
 1147                {
 11648                    var attrName = attr.AttributeClass?.ToDisplayString();
 1149
 11650                    if (attrName == NeedlrAiAgentAttributeName)
 1151                    {
 2052                        agentAttrLocation = attr.ApplicationSyntaxReference?.SyntaxTree is { } tree
 2053                            ? Location.Create(tree, attr.ApplicationSyntaxReference.Span)
 2054                            : typeSymbol.Locations[0];
 1155                    }
 1156
 11657                    if (attrName == AgentGroupChatMemberAttributeName || attrName == AgentSequenceMemberAttributeName)
 458                        inTopology = true;
 1159
 11660                    if (attrName == AgentHandoffsToAttributeName)
 1161                    {
 462                        inTopology = true;
 1163
 1164                        // The handoff target is also a topology participant
 465                        if (attr.ConstructorArguments.Length >= 1
 466                            && attr.ConstructorArguments[0].Kind == TypedConstantKind.Type
 467                            && attr.ConstructorArguments[0].Value is INamedTypeSymbol targetType)
 1168                        {
 469                            topologyParticipants.Add(
 470                                targetType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
 1171                        }
 1172                    }
 1173                }
 1174
 12075                if (agentAttrLocation is not null)
 2076                    agents[fqn] = (typeSymbol, agentAttrLocation);
 1177
 12078                if (inTopology)
 879                    topologyParticipants.Add(fqn);
 13180            }, SymbolKind.NamedType);
 1981
 1182            compilationContext.RegisterCompilationEndAction(endContext =>
 1183            {
 1184                var participantSet = new HashSet<string>(topologyParticipants, StringComparer.Ordinal);
 1185
 6286                foreach (var kvp in agents)
 1187                {
 2088                    if (!participantSet.Contains(kvp.Key))
 1189                    {
 890                        endContext.ReportDiagnostic(Diagnostic.Create(
 891                            MafDiagnosticDescriptors.OrphanAgent,
 892                            kvp.Value.Location,
 893                            kvp.Value.Symbol.Name));
 1194                    }
 1195                }
 2296            });
 3097        });
 1998    }
 99}