< Summary

Information
Class: NexusLabs.Needlr.Generators.CodeGen.DecoratorsCodeGenerator
Assembly: NexusLabs.Needlr.Generators
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators/CodeGen/DecoratorsCodeGenerator.cs
Line coverage
84%
Covered lines: 90
Uncovered lines: 16
Coverable lines: 106
Total lines: 230
Line coverage: 84.9%
Branch coverage
81%
Covered branches: 36
Total branches: 44
Branch coverage: 81.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
GenerateApplyDecoratorsMethod(...)75%452466.66%
GenerateRegisterHostedServicesMethod(...)75%44100%
ExpandOpenDecorators(...)100%88100%
GenerateRegisterProvidersMethod(...)87.5%88100%

File(s)

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

#LineLine coverage
 1// Copyright (c) NexusLabs. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System.Collections.Generic;
 5using System.Linq;
 6using System.Text;
 7
 8using Microsoft.CodeAnalysis;
 9
 10using NexusLabs.Needlr.Generators.Models;
 11
 12namespace NexusLabs.Needlr.Generators.CodeGen;
 13
 14/// <summary>
 15/// Generates decorator registration, hosted service registration,
 16/// provider registration, and open-decorator expansion code.
 17/// </summary>
 18internal static class DecoratorsCodeGenerator
 19{
 20    /// <summary>
 21    /// Emits the <c>ApplyDecorators(IServiceCollection)</c> method body into the TypeRegistry class.
 22    /// </summary>
 23    internal static void GenerateApplyDecoratorsMethod(StringBuilder builder, IReadOnlyList<DiscoveredDecorator> decorat
 24    {
 45525        builder.AppendLine("    /// <summary>");
 45526        builder.AppendLine("    /// Applies all discovered decorators, interceptors, and hosted services to the service 
 45527        builder.AppendLine("    /// Decorators are applied in order, with lower Order values applied first (closer to th
 45528        builder.AppendLine("    /// </summary>");
 45529        builder.AppendLine("    /// <param name=\"services\">The service collection to apply decorators to.</param>");
 45530        builder.AppendLine("    public static void ApplyDecorators(IServiceCollection services)");
 45531        builder.AppendLine("    {");
 32
 33        // Register ServiceCatalog first
 45534        breadcrumbs.WriteInlineComment(builder, "        ", "Register service catalog for DI resolution");
 45535        builder.AppendLine($"        services.AddSingleton<global::NexusLabs.Needlr.Catalog.IServiceCatalog, global::{sa
 45536        builder.AppendLine();
 37
 38        // Register hosted services first (before decorators apply)
 45539        if (hasHostedServices)
 40        {
 641            breadcrumbs.WriteInlineComment(builder, "        ", "Register hosted services");
 642            builder.AppendLine("        RegisterHostedServices(services);");
 643            if (decorators.Count > 0 || hasInterceptors)
 44            {
 045                builder.AppendLine();
 46            }
 47        }
 48
 45549        if (decorators.Count == 0 && !hasInterceptors)
 50        {
 42451            if (!hasHostedServices)
 52            {
 41853                breadcrumbs.WriteInlineComment(builder, "        ", "No decorators, interceptors, or hosted services dis
 54            }
 55        }
 56        else
 57        {
 3158            if (decorators.Count > 0)
 59            {
 60                // Group decorators by service type and order by Order property
 1761                var decoratorsByService = decorators
 2662                    .GroupBy(d => d.ServiceTypeName)
 3763                    .OrderBy(g => g.Key);
 64
 7465                foreach (var serviceGroup in decoratorsByService)
 66                {
 67                    // Write verbose breadcrumb for decorator chain
 2068                    if (breadcrumbs.Level == BreadcrumbLevel.Verbose)
 69                    {
 070                        var chainItems = serviceGroup.OrderBy(d => d.Order).ToList();
 071                        var lines = new List<string>
 072                        {
 073                            "Resolution order (outer → inner → target):"
 074                        };
 075                        for (int i = 0; i < chainItems.Count; i++)
 76                        {
 077                            var dec = chainItems[i];
 078                            var sourcePath = dec.SourceFilePath != null
 079                                ? BreadcrumbWriter.GetRelativeSourcePath(dec.SourceFilePath, projectDirectory)
 080                                : $"[{dec.AssemblyName}]";
 081                            lines.Add($"  {i + 1}. {dec.DecoratorTypeName.Split('.').Last()} (Order={dec.Order}) ← {sour
 82                        }
 083                        lines.Add($"Triggered by: [DecoratorFor<{serviceGroup.Key.Split('.').Last()}>] attributes");
 84
 085                        breadcrumbs.WriteVerboseBox(builder, "        ",
 086                            $"Decorator Chain: {serviceGroup.Key.Split('.').Last()}",
 087                            lines.ToArray());
 88                    }
 89                    else
 90                    {
 2091                        breadcrumbs.WriteInlineComment(builder, "        ", $"Decorators for {serviceGroup.Key}");
 92                    }
 93
 11894                    foreach (var decorator in serviceGroup.OrderBy(d => d.Order))
 95                    {
 2696                        builder.AppendLine($"        services.AddDecorator<{decorator.ServiceTypeName}, {decorator.Decor
 97                    }
 98                }
 99            }
 100
 31101            if (hasInterceptors)
 102            {
 14103                builder.AppendLine();
 14104                breadcrumbs.WriteInlineComment(builder, "        ", "Register intercepted services with their proxies");
 14105                builder.AppendLine($"        global::{safeAssemblyName}.Generated.InterceptorRegistrations.RegisterInter
 106            }
 107        }
 108
 455109        builder.AppendLine("    }");
 455110    }
 111
 112    /// <summary>
 113    /// Emits the <c>RegisterHostedServices(IServiceCollection)</c> method body.
 114    /// </summary>
 115    internal static void GenerateRegisterHostedServicesMethod(StringBuilder builder, IReadOnlyList<DiscoveredHostedServi
 116    {
 6117        builder.AppendLine("    /// <summary>");
 6118        builder.AppendLine("    /// Registers all discovered hosted services (BackgroundService and IHostedService imple
 6119        builder.AppendLine("    /// Each service is registered as singleton and also as IHostedService for the host to d
 6120        builder.AppendLine("    /// </summary>");
 6121        builder.AppendLine("    /// <param name=\"services\">The service collection to register to.</param>");
 6122        builder.AppendLine("    private static void RegisterHostedServices(IServiceCollection services)");
 6123        builder.AppendLine("    {");
 124
 26125        foreach (var hostedService in hostedServices)
 126        {
 7127            var typeName = hostedService.TypeName;
 7128            var shortName = typeName.Split('.').Last();
 7129            var sourcePath = hostedService.SourceFilePath != null
 7130                ? BreadcrumbWriter.GetRelativeSourcePath(hostedService.SourceFilePath, projectDirectory)
 7131                : $"[{hostedService.AssemblyName}]";
 132
 7133            breadcrumbs.WriteInlineComment(builder, "        ", $"Hosted service: {shortName} ← {sourcePath}");
 134
 135            // Register the concrete type as singleton
 7136            builder.AppendLine($"        services.AddSingleton<{typeName}>();");
 137
 138            // Register as IHostedService that forwards to the concrete type
 7139            builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Hosting.IHostedService>(sp =
 140        }
 141
 6142        builder.AppendLine("    }");
 6143    }
 144
 145    /// <summary>
 146    /// Expands open generic decorators into concrete decorator registrations
 147    /// for each discovered closed implementation of the open generic interface.
 148    /// </summary>
 149    internal static void ExpandOpenDecorators(
 150        IReadOnlyList<DiscoveredType> injectableTypes,
 151        IReadOnlyList<DiscoveredOpenDecorator> openDecorators,
 152        List<DiscoveredDecorator> decorators)
 153    {
 40154        foreach (var discoveredType in injectableTypes)
 155        {
 156            // We need to check each interface this type implements to see if it's a closed version of an open generic
 60157            foreach (var openDecorator in openDecorators)
 158            {
 159                // Check if this type implements the open generic interface
 46160                foreach (var interfaceName in discoveredType.InterfaceNames)
 161                {
 162                    // This is string-based matching - we need to match the interface name pattern
 163                    // For example, if open generic is IHandler<> and the interface is IHandler<Order>, we should match
 7164                    var openGenericName = TypeDiscoveryHelper.GetFullyQualifiedName(openDecorator.OpenGenericInterface);
 165
 166                    // Extract the base name (before the <>)
 7167                    var openGenericBaseName = GeneratorHelpers.GetGenericBaseName(openGenericName);
 7168                    var interfaceBaseName = GeneratorHelpers.GetGenericBaseName(interfaceName);
 169
 7170                    if (openGenericBaseName == interfaceBaseName)
 171                    {
 172                        // This interface is a closed version of the open generic
 173                        // Create a closed decorator registration
 7174                        var closedDecoratorTypeName = GeneratorHelpers.CreateClosedGenericType(
 7175                            TypeDiscoveryHelper.GetFullyQualifiedName(openDecorator.DecoratorType),
 7176                            interfaceName,
 7177                            openGenericName);
 178
 7179                        decorators.Add(new DiscoveredDecorator(
 7180                            closedDecoratorTypeName,
 7181                            interfaceName,
 7182                            openDecorator.Order,
 7183                            openDecorator.AssemblyName,
 7184                            openDecorator.SourceFilePath));
 185                    }
 186                }
 187            }
 188        }
 6189    }
 190
 191    /// <summary>
 192    /// Emits the <c>RegisterProviders(IServiceCollection)</c> method body.
 193    /// </summary>
 194    internal static void GenerateRegisterProvidersMethod(StringBuilder builder, IReadOnlyList<DiscoveredProvider> provid
 195    {
 17196        builder.AppendLine("    /// <summary>");
 17197        builder.AppendLine("    /// Registers all generated providers as Singletons.");
 17198        builder.AppendLine("    /// Providers are strongly-typed service locators that expose services via typed propert
 17199        builder.AppendLine("    /// </summary>");
 17200        builder.AppendLine("    /// <param name=\"services\">The service collection to register to.</param>");
 17201        builder.AppendLine("    public static void RegisterProviders(IServiceCollection services)");
 17202        builder.AppendLine("    {");
 203
 70204        foreach (var provider in providers)
 205        {
 18206            var shortName = provider.SimpleTypeName;
 18207            var sourcePath = provider.SourceFilePath != null
 18208                ? BreadcrumbWriter.GetRelativeSourcePath(provider.SourceFilePath, projectDirectory)
 18209                : $"[{provider.AssemblyName}]";
 210
 18211            breadcrumbs.WriteInlineComment(builder, "        ", $"Provider: {shortName} ← {sourcePath}");
 212
 18213            if (provider.IsInterface)
 214            {
 215                // Interface mode: register the generated implementation
 12216                var implName = provider.ImplementationTypeName;
 12217                builder.AppendLine($"        services.AddSingleton<{provider.TypeName}, global::{safeAssemblyName}.Gener
 218            }
 6219            else if (provider.IsPartial)
 220            {
 221                // Shorthand class mode: register the partial class as its generated interface
 6222                var interfaceName = provider.InterfaceTypeName;
 6223                var providerNamespace = GeneratorHelpers.GetNamespaceFromTypeName(provider.TypeName);
 6224                builder.AppendLine($"        services.AddSingleton<global::{providerNamespace}.{interfaceName}, {provide
 225            }
 226        }
 227
 17228        builder.AppendLine("    }");
 17229    }
 230}