< 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    {
 1319        var proxyTypeName = GeneratorHelpers.GetProxyTypeName(service.TypeName);
 1320        var shortTypeName = GeneratorHelpers.GetShortTypeName(service.TypeName);
 21
 22        // Write verbose breadcrumb for interceptor proxy
 1323        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
 1357        builder.AppendLine("/// <summary>");
 1358        builder.AppendLine($"/// Interceptor proxy for {service.TypeName}.");
 1359        builder.AppendLine("/// Routes method calls through configured interceptors.");
 1360        builder.AppendLine("/// </summary>");
 1361        builder.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generators\", \"1
 62
 63        // Implement all interfaces
 1364        builder.Append($"internal sealed class {proxyTypeName}");
 1365        if (service.InterfaceNames.Length > 0)
 66        {
 1367            builder.Append(" : ");
 1368            builder.Append(string.Join(", ", service.InterfaceNames));
 69        }
 1370        builder.AppendLine();
 1371        builder.AppendLine("{");
 72
 73        // Fields
 1374        builder.AppendLine($"    private readonly {service.TypeName} _target;");
 1375        builder.AppendLine("    private readonly IServiceProvider _serviceProvider;");
 1376        builder.AppendLine();
 77
 78        // Static MethodInfo fields for each method
 1379        var methodIndex = 0;
 5280        foreach (var method in service.Methods)
 81        {
 1382            builder.AppendLine($"    internal static readonly MethodInfo _method{methodIndex} = typeof({method.Interface
 1383            methodIndex++;
 84        }
 1385        builder.AppendLine();
 86
 87        // Constructor
 1388        builder.AppendLine($"    public {proxyTypeName}({service.TypeName} target, IServiceProvider serviceProvider)");
 1389        builder.AppendLine("    {");
 1390        builder.AppendLine("        _target = target ?? throw new ArgumentNullException(nameof(target));");
 1391        builder.AppendLine("        _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(service
 1392        builder.AppendLine("    }");
 1393        builder.AppendLine();
 94
 95        // Generate each method
 1396        methodIndex = 0;
 5297        foreach (var method in service.Methods)
 98        {
 1399            GenerateInterceptedMethod(builder, method, methodIndex, service.TypeName, breadcrumbs);
 13100            methodIndex++;
 101        }
 102
 13103        builder.AppendLine("}");
 13104    }
 105
 106    internal static void GenerateInterceptedMethod(StringBuilder builder, InterceptorDiscoveryHelper.InterceptedMethodIn
 107    {
 13108        var parameterList = method.GetParameterList();
 13109        var argumentList = method.GetArgumentList();
 110
 111        // Write breadcrumb for method
 13112        if (method.InterceptorTypeNames.Length > 0)
 113        {
 29114            var interceptorNames = string.Join(" → ", method.InterceptorTypeNames.Select(t => t.Split('.').Last()));
 13115            breadcrumbs.WriteInlineComment(builder, "    ", $"{method.Name}: {interceptorNames}");
 116        }
 117        else
 118        {
 0119            breadcrumbs.WriteInlineComment(builder, "    ", $"{method.Name}: direct forward (no interceptors)");
 120        }
 121
 13122        builder.AppendLine($"    public {method.ReturnType} {method.Name}({parameterList})");
 13123        builder.AppendLine("    {");
 124
 125        // If no interceptors, just forward directly to target
 13126        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
 13142        var interceptorCount = method.InterceptorTypeNames.Length;
 13143        builder.AppendLine($"        var interceptors = new IMethodInterceptor[{interceptorCount}];");
 58144        for (var i = 0; i < interceptorCount; i++)
 145        {
 16146            builder.AppendLine($"        interceptors[{i}] = _serviceProvider.GetRequiredService<{method.InterceptorType
 147        }
 13148        builder.AppendLine();
 149
 150        // Create arguments array
 13151        if (method.Parameters.Count > 0)
 152        {
 24153            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        }
 13159        builder.AppendLine();
 160
 161        // Build the proceed chain - start from innermost (actual call) and wrap outward
 13162        builder.AppendLine("        // Build the interceptor chain from inside out");
 13163        builder.AppendLine("        Func<ValueTask<object?>> proceed = async () =>");
 13164        builder.AppendLine("        {");
 165
 13166        if (method.IsVoid)
 167        {
 2168            builder.AppendLine($"            _target.{method.Name}({argumentList});");
 2169            builder.AppendLine("            return null;");
 170        }
 11171        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        {
 10190            builder.AppendLine($"            var result = _target.{method.Name}({argumentList});");
 10191            builder.AppendLine("            return result;");
 192        }
 193
 13194        builder.AppendLine("        };");
 13195        builder.AppendLine();
 196
 197        // Wrap each interceptor, from last to first (so first interceptor is outermost)
 13198        builder.AppendLine("        for (var i = interceptors.Length - 1; i >= 0; i--)");
 13199        builder.AppendLine("        {");
 13200        builder.AppendLine("            var interceptor = interceptors[i];");
 13201        builder.AppendLine("            var nextProceed = proceed;");
 13202        builder.AppendLine($"            proceed = () => interceptor.InterceptAsync(new MethodInvocation(_target, _metho
 13203        builder.AppendLine("        }");
 13204        builder.AppendLine();
 205
 206        // Invoke the chain and return the result
 13207        if (method.IsVoid)
 208        {
 2209            builder.AppendLine("        proceed().AsTask().GetAwaiter().GetResult();");
 210        }
 11211        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
 10246            builder.AppendLine($"        return ({method.ReturnType})proceed().AsTask().GetAwaiter().GetResult()!;");
 247        }
 248
 13249        builder.AppendLine("    }");
 13250        builder.AppendLine();
 13251    }
 252}
 253