< Summary

Information
Class: NexusLabs.Needlr.AgentFramework.Analyzers.AgentGraphTerminalNodeAnalyzer
Assembly: NexusLabs.Needlr.AgentFramework.Analyzers
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework.Analyzers/AgentGraphTerminalNodeAnalyzer.cs
Line coverage
100%
Covered lines: 92
Uncovered lines: 0
Coverable lines: 92
Total lines: 123
Line coverage: 100%
Branch coverage
92%
Covered branches: 37
Total branches: 40
Branch coverage: 92.5%
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(...)92.5%4040100%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Collections.Concurrent;
 3using System.Collections.Generic;
 4using System.Collections.Immutable;
 5using System.Linq;
 6
 7using Microsoft.CodeAnalysis;
 8using Microsoft.CodeAnalysis.Diagnostics;
 9
 10namespace NexusLabs.Needlr.AgentFramework.Analyzers;
 11
 12/// <summary>
 13/// Analyzer that detects terminal nodes that have outgoing edges.
 14/// </summary>
 15/// <remarks>
 16/// <b>NDLRMAF027</b> (Error): A node marked as terminal via
 17/// <c>[AgentGraphNode(IsTerminal = true)]</c> also has <c>[AgentGraphEdge]</c> declarations.
 18/// Terminal nodes must not have outgoing edges.
 19/// </remarks>
 20[DiagnosticAnalyzer(LanguageNames.CSharp)]
 21public sealed class AgentGraphTerminalNodeAnalyzer : DiagnosticAnalyzer
 22{
 23    private const string AgentGraphEdgeAttributeName = "NexusLabs.Needlr.AgentFramework.AgentGraphEdgeAttribute";
 24    private const string AgentGraphNodeAttributeName = "NexusLabs.Needlr.AgentFramework.AgentGraphNodeAttribute";
 25
 26    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
 5727        ImmutableArray.Create(MafDiagnosticDescriptors.GraphTerminalNodeHasOutgoingEdges);
 28
 29    public override void Initialize(AnalysisContext context)
 30    {
 1131        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
 1132        context.EnableConcurrentExecution();
 33
 1134        context.RegisterCompilationStartAction(compilationContext =>
 1135        {
 1136            // Collect per-type: which graph names have IsTerminal=true, and which have outgoing edges
 1137            // fqn → { graphName → (isTerminal, hasEdges, symbol, terminalLocation) }
 638            var nodeData = new ConcurrentDictionary<string, ConcurrentDictionary<string, (bool IsTerminal, Location Term
 639                StringComparer.Ordinal);
 1140
 641            compilationContext.RegisterSymbolAction(symbolContext =>
 642            {
 9443                var typeSymbol = (INamedTypeSymbol)symbolContext.Symbol;
 9444                var fqn = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 645
 646                // Track terminal markers per graph
 9447                var terminalGraphs = new Dictionary<string, Location>(StringComparer.Ordinal);
 9448                var edgeGraphs = new HashSet<string>(StringComparer.Ordinal);
 649
 37050                foreach (var attr in typeSymbol.GetAttributes())
 651                {
 9152                    var attrName = attr.AttributeClass?.ToDisplayString();
 653
 9154                    if (attrName == AgentGraphNodeAttributeName)
 655                    {
 556                        if (attr.ConstructorArguments.Length < 1)
 657                            continue;
 658
 559                        if (attr.ConstructorArguments[0].Value is not string graphName)
 660                            continue;
 661
 562                        var isTerminal = false;
 1863                        foreach (var namedArg in attr.NamedArguments)
 664                        {
 465                            if (namedArg.Key == "IsTerminal" && namedArg.Value.Value is bool val)
 666                            {
 467                                isTerminal = val;
 668                            }
 669                        }
 670
 571                        if (isTerminal)
 672                        {
 473                            var location = attr.ApplicationSyntaxReference?.SyntaxTree is { } tree
 474                                ? Location.Create(tree, attr.ApplicationSyntaxReference.Span)
 475                                : typeSymbol.Locations[0];
 676
 477                            terminalGraphs[graphName] = location;
 678                        }
 679                    }
 680
 9181                    if (attrName == AgentGraphEdgeAttributeName)
 682                    {
 483                        if (attr.ConstructorArguments.Length < 1)
 684                            continue;
 685
 486                        if (attr.ConstructorArguments[0].Value is string graphName)
 687                        {
 488                            edgeGraphs.Add(graphName);
 689                        }
 690                    }
 691                }
 692
 693                // Check for conflicts
 19694                foreach (var kvp in terminalGraphs)
 695                {
 496                    if (edgeGraphs.Contains(kvp.Key))
 697                    {
 498                        var perNode = nodeData.GetOrAdd(fqn, _ => new ConcurrentDictionary<string, (bool, Location, bool
 299                        perNode[kvp.Key] = (true, kvp.Value, true, typeSymbol);
 6100                    }
 6101                }
 100102            }, SymbolKind.NamedType);
 11103
 6104            compilationContext.RegisterCompilationEndAction(endContext =>
 6105            {
 16106                foreach (var nodeKvp in nodeData)
 6107                {
 8108                    foreach (var graphKvp in nodeKvp.Value)
 6109                    {
 2110                        if (graphKvp.Value.IsTerminal && graphKvp.Value.HasEdges)
 6111                        {
 2112                            endContext.ReportDiagnostic(Diagnostic.Create(
 2113                                MafDiagnosticDescriptors.GraphTerminalNodeHasOutgoingEdges,
 2114                                graphKvp.Value.TerminalLocation,
 2115                                graphKvp.Value.Symbol.Name,
 2116                                graphKvp.Key));
 6117                        }
 6118                    }
 6119                }
 12120            });
 17121        });
 11122    }
 123}