< Summary

Information
Class: NexusLabs.Needlr.Generators.CodeGen.InterceptorCodeGenerator
Assembly: NexusLabs.Needlr.Generators
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators/CodeGen/InterceptorCodeGenerator.cs
Line coverage
88%
Covered lines: 112
Uncovered lines: 14
Coverable lines: 126
Total lines: 253
Line coverage: 88.8%
Branch coverage
68%
Covered branches: 30
Total branches: 44
Branch coverage: 68.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
GenerateInterceptorProxyClass(...)78.57%141498.33%
GenerateInterceptedMethod(...)63.33%373080.3%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators/CodeGen/InterceptorCodeGenerator.cs

#LineLine coverage
 1// Copyright (c) NexusLabs. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System;
 5using System.Linq;
 6using System.Text;
 7
 8using NexusLabs.Needlr.Generators.Models;
 9
 10namespace NexusLabs.Needlr.Generators.CodeGen;
 11
 12/// <summary>
 13/// Generates interceptor proxy classes for AOP-style method interception.
 14/// </summary>
 15internal static class InterceptorCodeGenerator
 16{
 17    internal static void GenerateInterceptorProxyClass(StringBuilder builder, DiscoveredInterceptedService service, Brea
 18    {
 1519        var proxyTypeName = GeneratorHelpers.GetProxyTypeName(service.TypeName);
 1520        var shortTypeName = GeneratorHelpers.GetShortTypeName(service.TypeName);
 21
 22        // Write verbose breadcrumb for interceptor proxy
 1523        if (breadcrumbs.Level == BreadcrumbLevel.Verbose)
 24        {
 325            var sourcePath = service.SourceFilePath != null
 326                ? BreadcrumbWriter.GetRelativeSourcePath(service.SourceFilePath, projectDirectory)
 327                : $"[{service.AssemblyName}]";
 28
 329            var interceptorsList = service.AllInterceptorTypeNames
 530                .Select((t, i) => $"  {i + 1}. {t.Split('.').Last()}")
 331                .ToArray();
 32
 333            var proxiedMethods = service.Methods
 334                .Where(m => m.InterceptorTypeNames.Length > 0)
 335                .Select(m => m.Name)
 336                .ToArray();
 337            var forwardedMethods = service.Methods
 338                .Where(m => m.InterceptorTypeNames.Length == 0)
 039                .Select(m => m.Name)
 340                .ToArray();
 41
 342            var lines = new List<string>
 343            {
 344                $"Source: {sourcePath}",
 345                $"Target Interface: {string.Join(", ", service.InterfaceNames.Select(i => i.Split('.').Last()))}",
 346                "Interceptors (execution order):"
 347            };
 348            lines.AddRange(interceptorsList);
 349            lines.Add($"Methods proxied: {(proxiedMethods.Length > 0 ? string.Join(", ", proxiedMethods) : "none")}");
 350            lines.Add($"Methods forwarded: {(forwardedMethods.Length > 0 ? string.Join(", ", forwardedMethods) : "none")
 51
 352            breadcrumbs.WriteVerboseBox(builder, "",
 353                $"Interceptor Proxy: {shortTypeName}",
 354                lines.ToArray());
 55        }
 56
 1557        builder.AppendLine("/// <summary>");
 1558        builder.AppendLine($"/// Interceptor proxy for {service.TypeName}.");
 1559        builder.AppendLine("/// Routes method calls through configured interceptors.");
 1560        builder.AppendLine("/// </summary>");
 1561        builder.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generators\", \"1
 62
 63        // Implement all interfaces
 1564        builder.Append($"internal sealed class {proxyTypeName}");
 1565        if (service.InterfaceNames.Length > 0)
 66        {
 1567            builder.Append(" : ");
 1568            builder.Append(string.Join(", ", service.InterfaceNames));
 69        }
 1570        builder.AppendLine();
 1571        builder.AppendLine("{");
 72
 73        // Fields
 1574        builder.AppendLine($"    private readonly {service.TypeName} _target;");
 1575        builder.AppendLine("    private readonly IServiceProvider _serviceProvider;");
 1576        builder.AppendLine();
 77
 78        // Static MethodInfo fields for each method
 1579        var methodIndex = 0;
 6080        foreach (var method in service.Methods)
 81        {
 1582            builder.AppendLine($"    internal static readonly MethodInfo _method{methodIndex} = typeof({method.Interface
 1583            methodIndex++;
 84        }
 1585        builder.AppendLine();
 86
 87        // Constructor
 1588        builder.AppendLine($"    public {proxyTypeName}({service.TypeName} target, IServiceProvider serviceProvider)");
 1589        builder.AppendLine("    {");
 1590        builder.AppendLine("        _target = target ?? throw new ArgumentNullException(nameof(target));");
 1591        builder.AppendLine("        _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(service
 1592        builder.AppendLine("    }");
 1593        builder.AppendLine();
 94
 95        // Generate each method
 1596        methodIndex = 0;
 6097        foreach (var method in service.Methods)
 98        {
 1599            GenerateInterceptedMethod(builder, method, methodIndex, service.TypeName, breadcrumbs);
 15100            methodIndex++;
 101        }
 102
 15103        builder.AppendLine("}");
 15104    }
 105
 106    internal static void GenerateInterceptedMethod(StringBuilder builder, InterceptorDiscoveryHelper.InterceptedMethodIn
 107    {
 15108        var parameterList = method.GetParameterList();
 15109        var argumentList = method.GetArgumentList();
 110
 111        // Write breadcrumb for method
 15112        if (method.InterceptorTypeNames.Length > 0)
 113        {
 33114            var interceptorNames = string.Join(" → ", method.InterceptorTypeNames.Select(t => t.Split('.').Last()));
 15115            breadcrumbs.WriteInlineComment(builder, "    ", $"{method.Name}: {interceptorNames}");
 116        }
 117        else
 118        {
 0119            breadcrumbs.WriteInlineComment(builder, "    ", $"{method.Name}: direct forward (no interceptors)");
 120        }
 121
 15122        builder.AppendLine($"    public {method.ReturnType} {method.Name}({parameterList})");
 15123        builder.AppendLine("    {");
 124
 125        // If no interceptors, just forward directly to target
 15126        if (method.InterceptorTypeNames.Length == 0)
 127        {
 0128            if (method.IsVoid)
 129            {
 0130                builder.AppendLine($"        _target.{method.Name}({argumentList});");
 131            }
 132            else
 133            {
 0134                builder.AppendLine($"        return _target.{method.Name}({argumentList});");
 135            }
 0136            builder.AppendLine("    }");
 0137            builder.AppendLine();
 0138            return;
 139        }
 140
 141        // Build interceptor chain
 15142        var interceptorCount = method.InterceptorTypeNames.Length;
 15143        builder.AppendLine($"        var interceptors = new IMethodInterceptor[{interceptorCount}];");
 66144        for (var i = 0; i < interceptorCount; i++)
 145        {
 18146            builder.AppendLine($"        interceptors[{i}] = _serviceProvider.GetRequiredService<{method.InterceptorType
 147        }
 15148        builder.AppendLine();
 149
 150        // Create arguments array
 15151        if (method.Parameters.Count > 0)
 152        {
 30153            builder.AppendLine($"        var args = new object?[] {{ {string.Join(", ", method.Parameters.Select(p => p.
 154        }
 155        else
 156        {
 1157            builder.AppendLine("        var args = Array.Empty<object?>();");
 158        }
 15159        builder.AppendLine();
 160
 161        // Build the proceed chain - start from innermost (actual call) and wrap outward
 15162        builder.AppendLine("        // Build the interceptor chain from inside out");
 15163        builder.AppendLine("        Func<ValueTask<object?>> proceed = async () =>");
 15164        builder.AppendLine("        {");
 165
 15166        if (method.IsVoid)
 167        {
 2168            builder.AppendLine($"            _target.{method.Name}({argumentList});");
 2169            builder.AppendLine("            return null;");
 170        }
 13171        else if (method.IsAsync)
 172        {
 173            // Check if the return type is Task<T> or ValueTask<T> (has a result)
 1174            var hasResult = !method.ReturnType.Equals("global::System.Threading.Tasks.Task", StringComparison.Ordinal) &
 1175                           !method.ReturnType.Equals("global::System.Threading.Tasks.ValueTask", StringComparison.Ordina
 176
 1177            if (hasResult)
 178            {
 1179                builder.AppendLine($"            var result = await _target.{method.Name}({argumentList});");
 1180                builder.AppendLine("            return result;");
 181            }
 182            else
 183            {
 0184                builder.AppendLine($"            await _target.{method.Name}({argumentList});");
 0185                builder.AppendLine("            return null;");
 186            }
 187        }
 188        else
 189        {
 12190            builder.AppendLine($"            var result = _target.{method.Name}({argumentList});");
 12191            builder.AppendLine("            return result;");
 192        }
 193
 15194        builder.AppendLine("        };");
 15195        builder.AppendLine();
 196
 197        // Wrap each interceptor, from last to first (so first interceptor is outermost)
 15198        builder.AppendLine("        for (var i = interceptors.Length - 1; i >= 0; i--)");
 15199        builder.AppendLine("        {");
 15200        builder.AppendLine("            var interceptor = interceptors[i];");
 15201        builder.AppendLine("            var nextProceed = proceed;");
 15202        builder.AppendLine($"            proceed = () => interceptor.InterceptAsync(new MethodInvocation(_target, _metho
 15203        builder.AppendLine("        }");
 15204        builder.AppendLine();
 205
 206        // Invoke the chain and return the result
 15207        if (method.IsVoid)
 208        {
 2209            builder.AppendLine("        proceed().AsTask().GetAwaiter().GetResult();");
 210        }
 13211        else if (method.IsAsync)
 212        {
 1213            var hasResult = !method.ReturnType.Equals("global::System.Threading.Tasks.Task", StringComparison.Ordinal) &
 1214                           !method.ReturnType.Equals("global::System.Threading.Tasks.ValueTask", StringComparison.Ordina
 215
 1216            if (hasResult)
 217            {
 218                // Extract the inner type from Task<T> or ValueTask<T>
 1219                var innerType = GeneratorHelpers.ExtractGenericTypeArgument(method.ReturnType);
 1220                if (method.ReturnType.StartsWith("global::System.Threading.Tasks.ValueTask<", StringComparison.Ordinal))
 221                {
 0222                    builder.AppendLine($"        return new {method.ReturnType}(proceed().AsTask().ContinueWith(t => ({i
 223                }
 224                else
 225                {
 226                    // Task<T>
 1227                    builder.AppendLine($"        return proceed().AsTask().ContinueWith(t => ({innerType})t.Result!);");
 228                }
 229            }
 230            else
 231            {
 232                // Task or ValueTask without result
 0233                if (method.ReturnType.StartsWith("global::System.Threading.Tasks.ValueTask", StringComparison.Ordinal))
 234                {
 0235                    builder.AppendLine("        return new global::System.Threading.Tasks.ValueTask(proceed().AsTask());
 236                }
 237                else
 238                {
 0239                    builder.AppendLine("        return proceed().AsTask();");
 240                }
 241            }
 242        }
 243        else
 244        {
 245            // Synchronous with return value
 12246            builder.AppendLine($"        return ({method.ReturnType})proceed().AsTask().GetAwaiter().GetResult()!;");
 247        }
 248
 15249        builder.AppendLine("    }");
 15250        builder.AppendLine();
 15251    }
 252}
 253