< Summary

Information
Class: NexusLabs.Needlr.Generators.CodeGen.FactoryCodeGenerator
Assembly: NexusLabs.Needlr.Generators
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators/CodeGen/FactoryCodeGenerator.cs
Line coverage
99%
Covered lines: 133
Uncovered lines: 1
Coverable lines: 134
Total lines: 250
Line coverage: 99.2%
Branch coverage
86%
Covered branches: 43
Total branches: 50
Branch coverage: 86%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
GenerateFactoryInterface(...)80%1010100%
GenerateFactoryImplementation(...)85.71%1414100%
GenerateFuncRegistration(...)78.57%141494.73%
GenerateFactoriesSource(...)100%1212100%

File(s)

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

#LineLine coverage
 1// Copyright (c) NexusLabs. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System;
 5using System.Collections.Generic;
 6using System.Linq;
 7using System.Text;
 8
 9using NexusLabs.Needlr.Generators.Models;
 10
 11namespace NexusLabs.Needlr.Generators.CodeGen;
 12
 13/// <summary>
 14/// Generates factory interfaces and implementations for [GenerateFactory] attributed types.
 15/// </summary>
 16internal static class FactoryCodeGenerator
 17{
 18    internal static void GenerateFactoryInterface(StringBuilder builder, DiscoveredFactory factory, BreadcrumbWriter bre
 19    {
 2320        var factoryName = $"I{factory.SimpleTypeName}Factory";
 21
 2322        builder.AppendLine("/// <summary>");
 2323        builder.AppendLine($"/// Factory interface for creating instances of <see cref=\"{factory.TypeName}\"/>.");
 2324        builder.AppendLine("/// </summary>");
 2325        builder.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generators\", \"1
 2326        builder.AppendLine($"public interface {factoryName}");
 2327        builder.AppendLine("{");
 28
 29        // Generate Create method for each constructor
 9830        foreach (var ctor in factory.Constructors)
 31        {
 2632            var runtimeParamList = string.Join(", ", ctor.RuntimeParameters.Select(p =>
 2633            {
 3234                var simpleTypeName = GeneratorHelpers.GetSimpleTypeName(p.TypeName);
 3235                return $"{p.TypeName} {p.ParameterName ?? GeneratorHelpers.ToCamelCase(simpleTypeName)}";
 2636            }));
 37
 2638            builder.AppendLine($"    /// <summary>Creates a new instance of {factory.SimpleTypeName}.</summary>");
 39
 40            // Add <param> tags for documented runtime parameters
 11641            foreach (var param in ctor.RuntimeParameters)
 42            {
 3243                if (!string.IsNullOrWhiteSpace(param.DocumentationComment))
 44                {
 745                    var paramName = param.ParameterName ?? GeneratorHelpers.ToCamelCase(GeneratorHelpers.GetSimpleTypeNa
 746                    var escapedDoc = GeneratorHelpers.EscapeXmlContent(param.DocumentationComment!);
 747                    builder.AppendLine($"    /// <param name=\"{paramName}\">{escapedDoc}</param>");
 48                }
 49            }
 50
 2651            builder.AppendLine($"    {factory.ReturnTypeName} Create({runtimeParamList});");
 52        }
 53
 2354        builder.AppendLine("}");
 2355    }
 56
 57    internal static void GenerateFactoryImplementation(StringBuilder builder, DiscoveredFactory factory, BreadcrumbWrite
 58    {
 2359        var factoryInterfaceName = $"I{factory.SimpleTypeName}Factory";
 2360        var factoryImplName = $"{factory.SimpleTypeName}Factory";
 61
 62        // Collect all unique injectable parameters across all constructors
 2363        var allInjectableParams = factory.Constructors
 2664            .SelectMany(c => c.InjectableParameters)
 2365            .GroupBy(p => p.TypeName)
 2066            .Select(g => g.First())
 2367            .ToList();
 68
 2369        builder.AppendLine("/// <summary>");
 2370        builder.AppendLine($"/// Factory implementation for creating instances of <see cref=\"{factory.TypeName}\"/>.");
 2371        builder.AppendLine("/// </summary>");
 2372        builder.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generators\", \"1
 2373        builder.AppendLine($"internal sealed class {factoryImplName} : {factoryInterfaceName}");
 2374        builder.AppendLine("{");
 75
 76        // Fields for injectable dependencies
 8677        foreach (var param in allInjectableParams)
 78        {
 2079            var fieldName = "_" + GeneratorHelpers.ToCamelCase(GeneratorHelpers.GetSimpleTypeName(param.TypeName));
 2080            builder.AppendLine($"    private readonly {param.TypeName} {fieldName};");
 81        }
 82
 2383        builder.AppendLine();
 84
 85        // Constructor
 4386        var ctorParams = string.Join(", ", allInjectableParams.Select(p => $"{p.TypeName} {GeneratorHelpers.ToCamelCase(
 2387        builder.AppendLine($"    public {factoryImplName}({ctorParams})");
 2388        builder.AppendLine("    {");
 8689        foreach (var param in allInjectableParams)
 90        {
 2091            var fieldName = "_" + GeneratorHelpers.ToCamelCase(GeneratorHelpers.GetSimpleTypeName(param.TypeName));
 2092            var paramName = GeneratorHelpers.ToCamelCase(GeneratorHelpers.GetSimpleTypeName(param.TypeName));
 2093            builder.AppendLine($"        {fieldName} = {paramName};");
 94        }
 2395        builder.AppendLine("    }");
 2396        builder.AppendLine();
 97
 98        // Create methods for each constructor
 9899        foreach (var ctor in factory.Constructors)
 100        {
 26101            var runtimeParamList = string.Join(", ", ctor.RuntimeParameters.Select(p =>
 26102            {
 32103                var paramName = p.ParameterName ?? GeneratorHelpers.ToCamelCase(GeneratorHelpers.GetSimpleTypeName(p.Typ
 32104                return $"{p.TypeName} {paramName}";
 26105            }));
 106
 26107            builder.AppendLine($"    public {factory.ReturnTypeName} Create({runtimeParamList})");
 26108            builder.AppendLine("    {");
 26109            builder.Append($"        return new {factory.TypeName}(");
 110
 111            // Build constructor arguments - injectable first (from fields), then runtime
 26112            var allArgs = new List<string>();
 98113            foreach (var inj in ctor.InjectableParameters)
 114            {
 23115                var fieldName = "_" + GeneratorHelpers.ToCamelCase(GeneratorHelpers.GetSimpleTypeName(inj.TypeName));
 23116                allArgs.Add(fieldName);
 117            }
 116118            foreach (var rt in ctor.RuntimeParameters)
 119            {
 32120                var paramName = rt.ParameterName ?? GeneratorHelpers.ToCamelCase(GeneratorHelpers.GetSimpleTypeName(rt.T
 32121                allArgs.Add(paramName);
 122            }
 123
 26124            builder.Append(string.Join(", ", allArgs));
 26125            builder.AppendLine(");");
 26126            builder.AppendLine("    }");
 127        }
 128
 23129        builder.AppendLine("}");
 23130    }
 131
 132    internal static void GenerateFuncRegistration(StringBuilder builder, DiscoveredFactory factory, FactoryDiscoveryHelp
 133    {
 134        // Build Func<TRuntime..., TReturn> type - uses ReturnTypeName (interface if generic attribute used)
 58135        var runtimeTypes = string.Join(", ", ctor.RuntimeParameters.Select(p => p.TypeName));
 26136        var funcType = $"Func<{runtimeTypes}, {factory.ReturnTypeName}>";
 137
 138        // Build the lambda
 26139        var runtimeParams = string.Join(", ", ctor.RuntimeParameters.Select(p =>
 58140            p.ParameterName ?? GeneratorHelpers.ToCamelCase(GeneratorHelpers.GetSimpleTypeName(p.TypeName))));
 141
 26142        builder.AppendLine($"{indent}services.AddSingleton<{funcType}>(sp =>");
 26143        builder.AppendLine($"{indent}    ({runtimeParams}) => new {factory.TypeName}(");
 144
 145        // Build constructor call arguments
 26146        var allArgs = new List<string>();
 98147        foreach (var inj in ctor.InjectableParameters)
 148        {
 23149            if (inj.IsKeyed)
 150            {
 0151                allArgs.Add($"sp.GetRequiredKeyedService<{inj.TypeName}>(\"{GeneratorHelpers.EscapeStringLiteral(inj.Ser
 152            }
 153            else
 154            {
 23155                allArgs.Add($"sp.GetRequiredService<{inj.TypeName}>()");
 156            }
 157        }
 116158        foreach (var rt in ctor.RuntimeParameters)
 159        {
 32160            allArgs.Add(rt.ParameterName ?? GeneratorHelpers.ToCamelCase(GeneratorHelpers.GetSimpleTypeName(rt.TypeName)
 161        }
 162
 162163        for (int i = 0; i < allArgs.Count; i++)
 164        {
 55165            var arg = allArgs[i];
 55166            var isLast = i == allArgs.Count - 1;
 55167            builder.AppendLine($"{indent}        {arg}{(isLast ? ")" : ",")}");
 168        }
 26169        builder.AppendLine($"{indent});");
 26170    }
 171
 172    /// <summary>
 173    /// Generates the complete Factories.g.cs source file containing factory
 174    /// interfaces, implementations, and the FactoryRegistrations helper.
 175    /// </summary>
 176    internal static string GenerateFactoriesSource(IReadOnlyList<DiscoveredFactory> factories, string assemblyName, Brea
 177    {
 24178        var builder = new StringBuilder();
 24179        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblyName);
 180
 24181        breadcrumbs.WriteFileHeader(builder, assemblyName, "Needlr Generated Factories");
 24182        builder.AppendLine("#nullable enable");
 24183        builder.AppendLine();
 24184        builder.AppendLine("using System;");
 24185        builder.AppendLine();
 24186        builder.AppendLine("using Microsoft.Extensions.DependencyInjection;");
 24187        builder.AppendLine();
 24188        builder.AppendLine($"namespace {safeAssemblyName}.Generated;");
 24189        builder.AppendLine();
 190
 191        // Generate factory interfaces and implementations for each type
 96192        foreach (var factory in factories)
 193        {
 24194            if (factory.GenerateInterface)
 195            {
 23196                GenerateFactoryInterface(builder, factory, breadcrumbs, projectDirectory);
 23197                builder.AppendLine();
 23198                GenerateFactoryImplementation(builder, factory, breadcrumbs, projectDirectory);
 23199                builder.AppendLine();
 200            }
 201        }
 202
 203        // Generate the registration helper
 24204        builder.AppendLine("/// <summary>");
 24205        builder.AppendLine("/// Helper class for registering factory types.");
 24206        builder.AppendLine("/// </summary>");
 24207        builder.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generators\", \"1
 24208        builder.AppendLine("public static class FactoryRegistrations");
 24209        builder.AppendLine("{");
 24210        builder.AppendLine("    /// <summary>");
 24211        builder.AppendLine("    /// Registers all generated factories.");
 24212        builder.AppendLine("    /// </summary>");
 24213        builder.AppendLine("    /// <param name=\"services\">The service collection to register to.</param>");
 24214        builder.AppendLine("    public static void RegisterFactories(IServiceCollection services)");
 24215        builder.AppendLine("    {");
 216
 96217        foreach (var factory in factories)
 218        {
 24219            breadcrumbs.WriteInlineComment(builder, "        ", $"Factory for {factory.SimpleTypeName}");
 220
 221            // Register Func<> for each constructor
 24222            if (factory.GenerateFunc)
 223            {
 98224                foreach (var ctor in factory.Constructors)
 225                {
 26226                    GenerateFuncRegistration(builder, factory, ctor, "        ");
 227                }
 228            }
 229
 230            // Register interface factory
 24231            if (factory.GenerateInterface)
 232            {
 23233                var factoryInterfaceName = $"I{factory.SimpleTypeName}Factory";
 23234                var factoryImplName = $"{factory.SimpleTypeName}Factory";
 23235                builder.AppendLine($"        services.AddSingleton<global::{safeAssemblyName}.Generated.{factoryInterfac
 236            }
 237        }
 238
 24239        builder.AppendLine("    }");
 24240        builder.AppendLine();
 24241        builder.AppendLine("    /// <summary>");
 24242        builder.AppendLine("    /// Gets the number of factory types generated at compile time.");
 24243        builder.AppendLine("    /// </summary>");
 24244        builder.AppendLine($"    public static int Count => {factories.Count};");
 24245        builder.AppendLine("}");
 246
 24247        return builder.ToString();
 248    }
 249}
 250