< Summary

Information
Class: NexusLabs.Needlr.Injection.SourceGen.TypeRegistrars.GeneratedTypeRegistrar
Assembly: NexusLabs.Needlr.Injection.SourceGen
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Injection.SourceGen/TypeRegistrars/GeneratedTypeRegistrar.cs
Line coverage
91%
Covered lines: 52
Uncovered lines: 5
Coverable lines: 57
Total lines: 192
Line coverage: 91.2%
Branch coverage
88%
Covered branches: 37
Total branches: 42
Branch coverage: 88%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%210%
.ctor(...)100%11100%
RegisterTypesFromAssemblies(...)85.71%1414100%
ConvertToFiltererLifetime(...)75%4485.71%
ConvertToServiceLifetime(...)75%4485.71%
RegisterTypeAsSelfWithInterfaces(...)80%232080.76%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Injection.SourceGen/TypeRegistrars/GeneratedTypeRegistrar.cs

#LineLine coverage
 1using Microsoft.Extensions.DependencyInjection;
 2
 3using NexusLabs.Needlr.Generators;
 4
 5using System.Reflection;
 6
 7namespace NexusLabs.Needlr.Injection.SourceGen.TypeRegistrars;
 8
 9/// <summary>
 10/// A type registrar that uses compile-time generated type registry
 11/// instead of runtime reflection for type discovery.
 12/// </summary>
 13/// <remarks>
 14/// <para>
 15/// This registrar is designed to work with the source generator from
 16/// <c>NexusLabs.Needlr.Generators</c>. To use it:
 17/// </para>
 18/// <list type="number">
 19/// <item>Add the <c>NexusLabs.Needlr.Generators</c> package to your project</item>
 20/// <item>Add <c>[assembly: GenerateTypeRegistry(...)]</c> attribute</item>
 21/// <item>Use <c>.UsingGeneratedTypeRegistrar()</c> when building the service provider</item>
 22/// </list>
 23/// <para>
 24/// The assemblies parameter is ignored when a type provider is supplied,
 25/// as all type discovery happens at compile time.
 26/// </para>
 27/// </remarks>
 28/// <example>
 29/// <code>
 30/// // In your assembly (typically Program.cs or a dedicated file):
 31/// [assembly: GenerateTypeRegistry(IncludeNamespacePrefixes = new[] { "MyCompany", "NexusLabs" })]
 32///
 33/// // When building the service provider:
 34/// var serviceProvider = new Syringe()
 35///     .UsingGeneratedTypeRegistrar()
 36///     .BuildServiceProvider(config);
 37/// </code>
 38/// </example>
 39public sealed class GeneratedTypeRegistrar : ITypeRegistrar
 40{
 41    private readonly Func<IReadOnlyList<InjectableTypeInfo>>? _typeProvider;
 42
 43    /// <summary>
 44    /// Initializes a new instance using the default generated TypeRegistry.
 45    /// </summary>
 46    /// <remarks>
 47    /// This constructor uses reflection to locate the generated TypeRegistry class.
 48    /// The generated class must be present in the calling assembly.
 49    /// </remarks>
 50    public GeneratedTypeRegistrar()
 051        : this(null)
 52    {
 053    }
 54
 55    /// <summary>
 56    /// Initializes a new instance with a custom type provider.
 57    /// </summary>
 58    /// <param name="typeProvider">
 59    /// A function that returns the injectable types.
 60    /// If null, the registrar will attempt to locate the generated TypeRegistry at runtime.
 61    /// </param>
 27562    public GeneratedTypeRegistrar(Func<IReadOnlyList<InjectableTypeInfo>>? typeProvider)
 63    {
 27564        _typeProvider = typeProvider;
 27565    }
 66
 67    /// <inheritdoc />
 68    public void RegisterTypesFromAssemblies(
 69        IServiceCollection services,
 70        ITypeFilterer typeFilterer,
 71        IReadOnlyList<Assembly> assemblies)
 72    {
 25973        var types = _typeProvider?.Invoke() ?? [];
 74
 7294775        foreach (var typeInfo in types)
 76        {
 77            // Use pre-computed lifetime - no reflection needed
 78            // The source generator only emits types with valid lifetimes
 3621579            if (!typeInfo.Lifetime.HasValue)
 80            {
 81                continue;
 82            }
 83
 3621484            var type = typeInfo.Type;
 85
 86            // Check if type is excluded via Except<T>() or Except(predicate)
 3621487            if (typeFilterer.IsTypeExcluded(type))
 88            {
 89                continue;
 90            }
 91
 92            // Get effective lifetime, allowing type filterer to override pre-computed lifetime
 93            // This enables UsingOnlyAsTransient<T>(), UsingOnlyAsSingleton<T>(), etc.
 3618994            var defaultLifetime = ConvertToFiltererLifetime(typeInfo.Lifetime.Value);
 3618995            var effectiveLifetime = typeFilterer.GetEffectiveLifetime(type, defaultLifetime);
 3618996            var serviceLifetime = ConvertToServiceLifetime(effectiveLifetime);
 97
 3618998            RegisterTypeAsSelfWithInterfaces(services, typeInfo, serviceLifetime);
 99        }
 100
 101        // Apply decorators from the source generator
 258102        if (NeedlrSourceGenBootstrap.TryGetDecoratorApplier(out var decoratorApplier) && decoratorApplier is not null)
 103        {
 252104            decoratorApplier(services);
 105        }
 258106    }
 107
 108    private static TypeFiltererLifetime ConvertToFiltererLifetime(InjectableLifetime lifetime)
 109    {
 36189110        return lifetime switch
 36189111        {
 34747112            InjectableLifetime.Singleton => TypeFiltererLifetime.Singleton,
 721113            InjectableLifetime.Scoped => TypeFiltererLifetime.Scoped,
 721114            InjectableLifetime.Transient => TypeFiltererLifetime.Transient,
 0115            _ => TypeFiltererLifetime.Singleton
 36189116        };
 117    }
 118
 119    private static ServiceLifetime ConvertToServiceLifetime(TypeFiltererLifetime lifetime)
 120    {
 36189121        return lifetime switch
 36189122        {
 34737123            TypeFiltererLifetime.Singleton => ServiceLifetime.Singleton,
 721124            TypeFiltererLifetime.Scoped => ServiceLifetime.Scoped,
 731125            TypeFiltererLifetime.Transient => ServiceLifetime.Transient,
 0126            _ => ServiceLifetime.Singleton
 36189127        };
 128    }
 129
 130    private static void RegisterTypeAsSelfWithInterfaces(
 131        IServiceCollection services,
 132        InjectableTypeInfo typeInfo,
 133        ServiceLifetime lifetime)
 134    {
 36189135        var type = typeInfo.Type;
 36189136        var factory = typeInfo.Factory;
 137
 138        // Factory is always provided by source generator - required for AOT compatibility
 36189139        if (factory is null)
 140        {
 1141            throw new InvalidOperationException(
 1142                $"No factory delegate provided for type '{type.FullName}'. " +
 1143                "This indicates the type was not processed by the Needlr source generator. " +
 1144                "Ensure the type is included in the GenerateTypeRegistry namespace prefixes.");
 145        }
 146
 147        // Register as self using factory (AOT-compatible, no reflection needed)
 36188148        services.Add(new ServiceDescriptor(type, factory, lifetime));
 149
 150        // Register as interfaces
 105016151        foreach (var interfaceType in typeInfo.Interfaces)
 152        {
 153            // For singletons, register interfaces as factory delegates to ensure same instance
 16320154            if (lifetime == ServiceLifetime.Singleton)
 155            {
 14870156                services.Add(new ServiceDescriptor(
 14870157                    interfaceType,
 135158                    serviceProvider => serviceProvider.GetRequiredService(type),
 14870159                    lifetime));
 160            }
 161            else
 162            {
 163                // For transient/scoped services, use the factory
 1450164                services.Add(new ServiceDescriptor(interfaceType, factory, lifetime));
 165            }
 166        }
 167
 168        // Register keyed services from [Keyed] attributes
 73336169        foreach (var serviceKey in typeInfo.ServiceKeys)
 170        {
 171            // Register keyed self
 480172            services.Add(new ServiceDescriptor(type, serviceKey, (sp, _) => factory(sp), lifetime));
 173
 174            // Register keyed interfaces
 1920175            foreach (var interfaceType in typeInfo.Interfaces)
 176            {
 480177                if (lifetime == ServiceLifetime.Singleton)
 178                {
 480179                    services.Add(new ServiceDescriptor(
 480180                        interfaceType,
 480181                        serviceKey,
 2182                        (sp, _) => sp.GetRequiredService(type),
 480183                        lifetime));
 184                }
 185                else
 186                {
 0187                    services.Add(new ServiceDescriptor(interfaceType, serviceKey, (sp, _) => factory(sp), lifetime));
 188                }
 189            }
 190        }
 36188191    }
 192}