< Summary

Information
Class: NexusLabs.Needlr.AgentFramework.Analyzers.AgentGraphEntryPointAnalyzer
Assembly: NexusLabs.Needlr.AgentFramework.Analyzers
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework.Analyzers/AgentGraphEntryPointAnalyzer.cs
Line coverage
91%
Covered lines: 103
Uncovered lines: 9
Coverable lines: 112
Total lines: 146
Line coverage: 91.9%
Branch coverage
78%
Covered branches: 36
Total branches: 46
Branch coverage: 78.2%
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(...)78.26%474691.74%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework.Analyzers/AgentGraphEntryPointAnalyzer.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 validates entry point declarations for agent graphs.
 14/// </summary>
 15/// <remarks>
 16/// <para>
 17/// <b>NDLRMAF017</b> (Error): A named graph has edges but no <c>[AgentGraphEntry]</c>.
 18/// </para>
 19/// <para>
 20/// <b>NDLRMAF018</b> (Error): A named graph has multiple <c>[AgentGraphEntry]</c> declarations.
 21/// </para>
 22/// </remarks>
 23[DiagnosticAnalyzer(LanguageNames.CSharp)]
 24public sealed class AgentGraphEntryPointAnalyzer : DiagnosticAnalyzer
 25{
 26    private const string AgentGraphEdgeAttributeName = "NexusLabs.Needlr.AgentFramework.AgentGraphEdgeAttribute";
 27    private const string AgentGraphEntryAttributeName = "NexusLabs.Needlr.AgentFramework.AgentGraphEntryAttribute";
 28
 29    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
 11330        ImmutableArray.Create(
 11331            MafDiagnosticDescriptors.GraphNoEntryPoint,
 11332            MafDiagnosticDescriptors.GraphMultipleEntryPoints);
 33
 34    public override void Initialize(AnalysisContext context)
 35    {
 1236        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
 1237        context.EnableConcurrentExecution();
 38
 1239        context.RegisterCompilationStartAction(compilationContext =>
 1240        {
 1241            // graphName → list of (typeName, location)
 742            var entryPoints = new ConcurrentDictionary<string, ConcurrentBag<(string TypeName, Location Location)>>(Stri
 1243            // graphName → set of edge attribute locations (for reporting NDLRMAF017)
 744            var edgeGraphNames = new ConcurrentDictionary<string, ConcurrentBag<Location>>(StringComparer.Ordinal);
 1245
 746            compilationContext.RegisterSymbolAction(symbolContext =>
 747            {
 11448                var typeSymbol = (INamedTypeSymbol)symbolContext.Symbol;
 749
 46050                foreach (var attr in typeSymbol.GetAttributes())
 751                {
 11652                    var attrName = attr.AttributeClass?.ToDisplayString();
 753
 11654                    if (attrName == AgentGraphEntryAttributeName)
 755                    {
 756                        if (attr.ConstructorArguments.Length < 1)
 757                            continue;
 758
 759                        if (attr.ConstructorArguments[0].Value is not string graphName)
 760                            continue;
 761
 762                        var location = attr.ApplicationSyntaxReference?.SyntaxTree is { } tree
 763                            ? Location.Create(tree, attr.ApplicationSyntaxReference.Span)
 764                            : typeSymbol.Locations[0];
 765
 1266                        entryPoints.GetOrAdd(graphName, _ => new ConcurrentBag<(string, Location)>())
 767                            .Add((typeSymbol.Name, location));
 768                    }
 769
 11670                    if (attrName == AgentGraphEdgeAttributeName)
 771                    {
 972                        if (attr.ConstructorArguments.Length < 1)
 773                            continue;
 774
 975                        if (attr.ConstructorArguments[0].Value is not string graphName)
 776                            continue;
 777
 978                        var location = attr.ApplicationSyntaxReference?.SyntaxTree is { } tree
 979                            ? Location.Create(tree, attr.ApplicationSyntaxReference.Span)
 980                            : typeSymbol.Locations[0];
 781
 1682                        edgeGraphNames.GetOrAdd(graphName, _ => new ConcurrentBag<Location>())
 983                            .Add(location);
 784                    }
 785                }
 12186            }, SymbolKind.NamedType);
 1287
 788            compilationContext.RegisterCompilationEndAction(endContext =>
 789            {
 790                // Collect all graph names (union of entry and edge declarations)
 791                var allGraphNames = new HashSet<string>(edgeGraphNames.Keys, StringComparer.Ordinal);
 792
 2893                foreach (var graphName in allGraphNames)
 794                {
 795                    if (!entryPoints.TryGetValue(graphName, out var entries) || entries.Count == 0)
 796                    {
 797                        // NDLRMAF017: edges exist but no entry point
 298                        if (edgeGraphNames.TryGetValue(graphName, out var edgeLocations))
 799                        {
 2100                            var firstEdgeLocation = edgeLocations.First();
 2101                            endContext.ReportDiagnostic(Diagnostic.Create(
 2102                                MafDiagnosticDescriptors.GraphNoEntryPoint,
 2103                                firstEdgeLocation,
 2104                                graphName));
 7105                        }
 7106                    }
 5107                    else if (entries.Count > 1)
 7108                    {
 7109                        // NDLRMAF018: multiple entry points
 2110                        var entryList = entries.ToList();
 6111                        var typeNames = string.Join(", ", entryList.Select(e => e.TypeName));
 12112                        foreach (var entry in entryList)
 7113                        {
 4114                            endContext.ReportDiagnostic(Diagnostic.Create(
 4115                                MafDiagnosticDescriptors.GraphMultipleEntryPoints,
 4116                                entry.Location,
 4117                                graphName,
 4118                                typeNames));
 7119                        }
 7120                    }
 7121                }
 7122
 7123                // Also check entry-only graphs (entry declared but no edges) for multiple entries
 24124                foreach (var graphName in entryPoints.Keys)
 7125                {
 5126                    if (allGraphNames.Contains(graphName))
 7127                        continue;
 7128
 0129                    if (entryPoints.TryGetValue(graphName, out var entries) && entries.Count > 1)
 7130                    {
 0131                        var entryList = entries.ToList();
 0132                        var typeNames = string.Join(", ", entryList.Select(e => e.TypeName));
 0133                        foreach (var entry in entryList)
 7134                        {
 0135                            endContext.ReportDiagnostic(Diagnostic.Create(
 0136                                MafDiagnosticDescriptors.GraphMultipleEntryPoints,
 0137                                entry.Location,
 0138                                graphName,
 0139                                typeNames));
 7140                        }
 7141                    }
 7142                }
 14143            });
 19144        });
 12145    }
 146}