< Summary

Information
Class: NexusLabs.Needlr.AgentFramework.Analyzers.AgentSequenceOrderAnalyzer
Assembly: NexusLabs.Needlr.AgentFramework.Analyzers
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework.Analyzers/AgentSequenceOrderAnalyzer.cs
Line coverage
100%
Covered lines: 86
Uncovered lines: 0
Coverable lines: 86
Total lines: 117
Line coverage: 100%
Branch coverage
91%
Covered branches: 31
Total branches: 34
Branch coverage: 91.1%
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(...)91.17%3434100%

File(s)

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

#LineLine coverage
 1using System.Collections.Concurrent;
 2using System.Collections.Immutable;
 3using System.Linq;
 4
 5using Microsoft.CodeAnalysis;
 6using Microsoft.CodeAnalysis.Diagnostics;
 7
 8namespace NexusLabs.Needlr.AgentFramework.Analyzers;
 9
 10/// <summary>
 11/// Analyzer that validates <c>Order</c> values within <c>[AgentSequenceMember]</c> pipeline declarations.
 12/// </summary>
 13/// <remarks>
 14/// <para>
 15/// <b>NDLRMAF006</b> (Error): Two or more agents in the same pipeline declare the same <c>Order</c> value.
 16/// </para>
 17/// <para>
 18/// <b>NDLRMAF007</b> (Warning): The <c>Order</c> values in a pipeline are not contiguous — a gap exists.
 19/// </para>
 20/// </remarks>
 21[DiagnosticAnalyzer(LanguageNames.CSharp)]
 22public sealed class AgentSequenceOrderAnalyzer : DiagnosticAnalyzer
 23{
 24    private const string AgentSequenceMemberAttributeName = "NexusLabs.Needlr.AgentFramework.AgentSequenceMemberAttribut
 25
 26    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
 48927        ImmutableArray.Create(
 48928            MafDiagnosticDescriptors.DuplicateSequenceOrder,
 48929            MafDiagnosticDescriptors.GapInSequenceOrder);
 30
 31    public override void Initialize(AnalysisContext context)
 32    {
 2433        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
 2434        context.EnableConcurrentExecution();
 35
 2436        context.RegisterCompilationStartAction(compilationContext =>
 2437        {
 2438            // pipeline name → list of (agent type name, order, attribute location)
 1539            var pipelineEntries = new ConcurrentDictionary<string, ConcurrentBag<(string AgentName, int Order, Location 
 1540                StringComparer.Ordinal);
 2441
 1542            compilationContext.RegisterSymbolAction(symbolContext =>
 1543            {
 17944                var typeSymbol = (INamedTypeSymbol)symbolContext.Symbol;
 1545
 77446                foreach (var attr in typeSymbol.GetAttributes())
 1547                {
 20848                    if (attr.AttributeClass?.ToDisplayString() != AgentSequenceMemberAttributeName)
 1549                        continue;
 1550
 4451                    if (attr.ConstructorArguments.Length < 2)
 1552                        continue;
 1553
 4454                    if (attr.ConstructorArguments[0].Value is not string pipelineName
 4455                        || string.IsNullOrWhiteSpace(pipelineName))
 1556                        continue;
 1557
 4458                    if (attr.ConstructorArguments[1].Value is not int order)
 1559                        continue;
 1560
 4461                    var attrLocation = attr.ApplicationSyntaxReference?.SyntaxTree is { } tree
 4462                        ? Location.Create(tree, attr.ApplicationSyntaxReference.Span)
 4463                        : typeSymbol.Locations[0];
 1564
 6265                    pipelineEntries.GetOrAdd(pipelineName, _ => new ConcurrentBag<(string, int, Location)>())
 4466                        .Add((typeSymbol.Name, order, attrLocation));
 1567                }
 19468            }, SymbolKind.NamedType);
 2469
 1570            compilationContext.RegisterCompilationEndAction(endContext =>
 1571            {
 6672                foreach (var kvp in pipelineEntries)
 1573                {
 1874                    var pipelineName = kvp.Key;
 1875                    var entries = kvp.Value.ToList();
 1576
 1577                    // NDLRMAF006: duplicate Order values
 9678                    var duplicateGroups = entries.GroupBy(e => e.Order).Where(g => g.Count() > 1).ToList();
 5279                    foreach (var group in duplicateGroups)
 1580                    {
 5281                        foreach (var (agentName, order, location) in group)
 1582                        {
 1883                            endContext.ReportDiagnostic(Diagnostic.Create(
 1884                                MafDiagnosticDescriptors.DuplicateSequenceOrder,
 1885                                location,
 1886                                pipelineName,
 1887                                order,
 1888                                agentName));
 1589                        }
 1590                    }
 1591
 1592                    // NDLRMAF007: gap in Order sequence — only when no duplicate errors and 2+ members
 1893                    if (duplicateGroups.Count == 0 && entries.Count >= 2)
 1594                    {
 5595                        var sortedOrders = entries.Select(e => e.Order).OrderBy(o => o).ToList();
 3496                        for (var i = 1; i < sortedOrders.Count; i++)
 1597                        {
 1298                            if (sortedOrders[i] != sortedOrders[i - 1] + 1)
 1599                            {
 4100                                var missingOrder = sortedOrders[i - 1] + 1;
 32101                                foreach (var (_, _, location) in entries)
 15102                                {
 12103                                    endContext.ReportDiagnostic(Diagnostic.Create(
 12104                                        MafDiagnosticDescriptors.GapInSequenceOrder,
 12105                                        location,
 12106                                        pipelineName,
 12107                                        missingOrder));
 15108                                }
 15109                                break; // Report the first gap only per pipeline
 15110                            }
 15111                        }
 15112                    }
 15113                }
 30114            });
 39115        });
 24116    }
 117}