< Summary

Information
Class: NexusLabs.Needlr.AgentFramework.Analyzers.ToolResultToStringAnalyzer
Assembly: NexusLabs.Needlr.AgentFramework.Analyzers
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.AgentFramework.Analyzers/ToolResultToStringAnalyzer.cs
Line coverage
93%
Covered lines: 30
Uncovered lines: 2
Coverable lines: 32
Total lines: 90
Line coverage: 93.7%
Branch coverage
84%
Covered branches: 22
Total branches: 26
Branch coverage: 84.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
get_SupportedDiagnostics()100%11100%
Initialize(...)100%11100%
AnalyzeInvocation(...)84.61%262691.66%

File(s)

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

#LineLine coverage
 1using System.Collections.Immutable;
 2
 3using Microsoft.CodeAnalysis;
 4using Microsoft.CodeAnalysis.Diagnostics;
 5using Microsoft.CodeAnalysis.Operations;
 6
 7namespace NexusLabs.Needlr.AgentFramework.Analyzers;
 8
 9/// <summary>
 10/// Detects <c>.ToString()</c> invocations on <c>ToolCallResult.Result</c> and
 11/// <c>FunctionResultContent.Result</c> properties, which are <c>object?</c>
 12/// and may contain a <c>JsonElement</c> at runtime.
 13/// </summary>
 14/// <remarks>
 15/// <b>NDLRMAF015</b> (Warning): Calling <c>ToString()</c> on these properties
 16/// produces a C# type name for complex objects instead of JSON. Developers
 17/// should use <c>ToolResultSerializer.Serialize()</c> instead.
 18/// </remarks>
 19[DiagnosticAnalyzer(LanguageNames.CSharp)]
 20public sealed class ToolResultToStringAnalyzer : DiagnosticAnalyzer
 21{
 122    private static readonly ImmutableHashSet<string> TargetTypeNames = ImmutableHashSet.Create(
 123        "NexusLabs.Needlr.AgentFramework.Iterative.ToolCallResult",
 124        "Microsoft.Extensions.AI.FunctionResultContent");
 25
 26    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
 16727        ImmutableArray.Create(MafDiagnosticDescriptors.ToolResultToStringCall);
 28
 29    public override void Initialize(AnalysisContext context)
 30    {
 1731        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
 1732        context.EnableConcurrentExecution();
 1733        context.RegisterOperationAction(AnalyzeInvocation, OperationKind.Invocation);
 1734    }
 35
 36    private static void AnalyzeInvocation(OperationAnalysisContext context)
 37    {
 938        var invocation = (IInvocationOperation)context.Operation;
 39
 40        // We're looking for .ToString() calls
 941        if (invocation.TargetMethod.Name != "ToString" ||
 942            invocation.TargetMethod.Parameters.Length != 0)
 43        {
 044            return;
 45        }
 46
 47        // The receiver must be a property access to .Result
 48        // This handles both direct access (result.Result.ToString())
 49        // and null-conditional access (result.Result?.ToString())
 950        IPropertyReferenceOperation? propertyRef = null;
 51
 952        if (invocation.Instance is IPropertyReferenceOperation directProp)
 53        {
 754            propertyRef = directProp;
 55        }
 256        else if (invocation.Instance is IConditionalAccessInstanceOperation)
 57        {
 58            // For result.Result?.ToString(), walk up to the ConditionalAccessOperation
 59            // and check its operand
 260            var parent = invocation.Parent;
 261            while (parent is not null and not IConditionalAccessOperation)
 62            {
 063                parent = parent.Parent;
 64            }
 65
 266            if (parent is IConditionalAccessOperation conditional &&
 267                conditional.Operation is IPropertyReferenceOperation condProp)
 68            {
 269                propertyRef = condProp;
 70            }
 71        }
 72
 973        if (propertyRef is null || propertyRef.Property.Name != "Result")
 74        {
 275            return;
 76        }
 77
 78        // Check if the containing type is one of our targets
 779        var containingType = propertyRef.Property.ContainingType?.ToDisplayString();
 780        if (containingType is null || !TargetTypeNames.Contains(containingType))
 81        {
 182            return;
 83        }
 84
 685        context.ReportDiagnostic(Diagnostic.Create(
 686            MafDiagnosticDescriptors.ToolResultToStringCall,
 687            invocation.Syntax.GetLocation(),
 688            containingType));
 689    }
 90}