< Summary

Information
Class: NexusLabs.Needlr.Generators.CodeGen.ProviderCodeGenerator
Assembly: NexusLabs.Needlr.Generators
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators/CodeGen/ProviderCodeGenerator.cs
Line coverage
88%
Covered lines: 105
Uncovered lines: 13
Coverable lines: 118
Total lines: 201
Line coverage: 88.9%
Branch coverage
71%
Covered branches: 20
Total branches: 28
Branch coverage: 71.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
GenerateProviderImplementation(...)100%88100%
GenerateProviderInterfaceAndPartialClass(...)100%1212100%
GenerateProviderRegistration(...)0%2040%
GetNamespace(...)0%2040%

File(s)

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

#LineLine coverage
 1// Copyright (c) NexusLabs. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System.Linq;
 5using System.Text;
 6using NexusLabs.Needlr.Generators.Models;
 7
 8namespace NexusLabs.Needlr.Generators.CodeGen;
 9
 10/// <summary>
 11/// Generates Provider classes for [Provider] attributed types.
 12/// </summary>
 13internal static class ProviderCodeGenerator
 14{
 15    /// <summary>
 16    /// Generates provider implementation for an interface-based provider.
 17    /// </summary>
 18    internal static void GenerateProviderImplementation(StringBuilder builder, DiscoveredProvider provider, string gener
 19    {
 1220        var implName = provider.ImplementationTypeName;
 21
 1222        builder.AppendLine("/// <summary>");
 1223        builder.AppendLine($"/// Strongly-typed service provider implementing <see cref=\"{provider.TypeName}\"/>.");
 1224        builder.AppendLine("/// </summary>");
 1225        builder.AppendLine("/// <remarks>");
 1226        builder.AppendLine("/// <para>");
 1227        builder.AppendLine("/// This provider is registered as a <b>Singleton</b>. All service properties are resolved")
 1228        builder.AppendLine("/// at construction time via constructor injection for fail-fast error detection.");
 1229        builder.AppendLine("/// </para>");
 1230        builder.AppendLine("/// <para>");
 1231        builder.AppendLine("/// To create new instances on demand, use factory properties instead of direct service refe
 1232        builder.AppendLine("/// </para>");
 1233        builder.AppendLine("/// </remarks>");
 1234        builder.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generators\", \"1
 1235        builder.AppendLine($"public sealed class {implName} : {provider.TypeName}");
 1236        builder.AppendLine("{");
 37
 38        // Generate constructor with all properties (Required, Optional, Collection, Factory)
 39        // Optional parameters must come last in C#
 1240        var injectableProps = provider.Properties
 541            .OrderBy(p => p.Kind == ProviderPropertyKind.Optional ? 1 : 0)
 1242            .ToList();
 43
 1244        var ctorParams = injectableProps.Select(p =>
 1245        {
 1546            var paramName = GeneratorHelpers.ToCamelCase(p.PropertyName);
 1547            if (p.Kind == ProviderPropertyKind.Optional)
 1248            {
 249                return $"{p.ServiceTypeName}? {paramName} = null";
 1250            }
 1351            return $"{p.ServiceTypeName} {paramName}";
 1252        });
 53
 1254        var ctorParamList = string.Join(", ", ctorParams);
 55
 1256        builder.AppendLine($"    /// <summary>");
 1257        builder.AppendLine($"    /// Creates a new instance of <see cref=\"{implName}\"/>.");
 1258        builder.AppendLine($"    /// </summary>");
 1259        builder.AppendLine($"    public {implName}({ctorParamList})");
 1260        builder.AppendLine("    {");
 61
 5462        foreach (var prop in injectableProps)
 63        {
 1564            var paramName = GeneratorHelpers.ToCamelCase(prop.PropertyName);
 1565            builder.AppendLine($"        {prop.PropertyName} = {paramName};");
 66        }
 67
 1268        builder.AppendLine("    }");
 1269        builder.AppendLine();
 70
 71        // Generate properties
 5472        foreach (var prop in provider.Properties)
 73        {
 1574            var propTypeName = prop.Kind == ProviderPropertyKind.Optional
 1575                ? $"{prop.ServiceTypeName}?"
 1576                : prop.ServiceTypeName;
 77
 1578            builder.AppendLine($"    /// <inheritdoc />");
 1579            builder.AppendLine($"    public {propTypeName} {prop.PropertyName} {{ get; }}");
 80        }
 81
 1282        builder.AppendLine("}");
 1283    }
 84
 85    /// <summary>
 86    /// Generates interface and partial class for shorthand [Provider(typeof(T))] on a class.
 87    /// </summary>
 88    internal static void GenerateProviderInterfaceAndPartialClass(StringBuilder builder, DiscoveredProvider provider, st
 89    {
 690        var interfaceName = provider.InterfaceTypeName;
 691        var className = provider.SimpleTypeName;
 92
 93        // Generate interface
 694        builder.AppendLine("/// <summary>");
 695        builder.AppendLine($"/// Interface for strongly-typed service provider generated from <see cref=\"{provider.Type
 696        builder.AppendLine("/// </summary>");
 697        builder.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generators\", \"1
 698        builder.AppendLine($"public interface {interfaceName}");
 699        builder.AppendLine("{");
 100
 26101        foreach (var prop in provider.Properties)
 102        {
 7103            var propTypeName = prop.Kind == ProviderPropertyKind.Optional
 7104                ? $"{prop.ServiceTypeName}?"
 7105                : prop.ServiceTypeName;
 106
 7107            builder.AppendLine($"    /// <summary>Gets the {prop.PropertyName} service.</summary>");
 7108            builder.AppendLine($"    {propTypeName} {prop.PropertyName} {{ get; }}");
 109        }
 110
 6111        builder.AppendLine("}");
 6112        builder.AppendLine();
 113
 114        // Generate partial class implementation
 6115        builder.AppendLine("/// <summary>");
 6116        builder.AppendLine("/// Strongly-typed service provider.");
 6117        builder.AppendLine("/// </summary>");
 6118        builder.AppendLine("/// <remarks>");
 6119        builder.AppendLine("/// <para>");
 6120        builder.AppendLine("/// This provider is registered as a <b>Singleton</b>. All service properties are resolved")
 6121        builder.AppendLine("/// at construction time via constructor injection for fail-fast error detection.");
 6122        builder.AppendLine("/// </para>");
 6123        builder.AppendLine("/// </remarks>");
 6124        builder.AppendLine($"public partial class {className} : {interfaceName}");
 6125        builder.AppendLine("{");
 126
 127        // Generate constructor with all properties (Required, Optional, Collection, Factory)
 128        // Optional parameters must come last in C#
 6129        var injectableProps = provider.Properties
 2130            .OrderBy(p => p.Kind == ProviderPropertyKind.Optional ? 1 : 0)
 6131            .ToList();
 132
 6133        var ctorParams = injectableProps.Select(p =>
 6134        {
 7135            var paramName = GeneratorHelpers.ToCamelCase(p.PropertyName);
 7136            if (p.Kind == ProviderPropertyKind.Optional)
 6137            {
 2138                return $"{p.ServiceTypeName}? {paramName} = null";
 6139            }
 5140            return $"{p.ServiceTypeName} {paramName}";
 6141        });
 142
 6143        var ctorParamList = string.Join(", ", ctorParams);
 144
 6145        builder.AppendLine($"    /// <summary>");
 6146        builder.AppendLine($"    /// Creates a new instance of <see cref=\"{className}\"/>.");
 6147        builder.AppendLine($"    /// </summary>");
 6148        builder.AppendLine($"    public {className}({ctorParamList})");
 6149        builder.AppendLine("    {");
 150
 26151        foreach (var prop in injectableProps)
 152        {
 7153            var paramName = GeneratorHelpers.ToCamelCase(prop.PropertyName);
 7154            builder.AppendLine($"        {prop.PropertyName} = {paramName};");
 155        }
 156
 6157        builder.AppendLine("    }");
 6158        builder.AppendLine();
 159
 160        // Generate properties
 26161        foreach (var prop in provider.Properties)
 162        {
 7163            var propTypeName = prop.Kind == ProviderPropertyKind.Optional
 7164                ? $"{prop.ServiceTypeName}?"
 7165                : prop.ServiceTypeName;
 166
 7167            builder.AppendLine($"    /// <inheritdoc />");
 7168            builder.AppendLine($"    public {propTypeName} {prop.PropertyName} {{ get; }}");
 169        }
 170
 6171        builder.AppendLine("}");
 6172    }
 173
 174    /// <summary>
 175    /// Generates provider registration code for the TypeRegistry.
 176    /// </summary>
 177    internal static void GenerateProviderRegistration(StringBuilder builder, DiscoveredProvider provider, string generat
 178    {
 0179        var interfaceTypeName = provider.IsInterface
 0180            ? provider.TypeName
 0181            : $"global::{GetNamespace(provider.TypeName)}.{provider.InterfaceTypeName}";
 182
 0183        var implTypeName = provider.IsInterface
 0184            ? $"global::{generatedNamespace}.{provider.ImplementationTypeName}"
 0185            : provider.TypeName;
 186
 0187        builder.AppendLine($"            // Provider: {provider.SimpleTypeName}");
 0188        builder.AppendLine($"            services.AddSingleton<{interfaceTypeName}, {implTypeName}>();");
 0189    }
 190
 191    private static string GetNamespace(string fullyQualifiedName)
 192    {
 0193        if (fullyQualifiedName.StartsWith("global::"))
 194        {
 0195            fullyQualifiedName = fullyQualifiedName.Substring(8);
 196        }
 197
 0198        var lastDot = fullyQualifiedName.LastIndexOf('.');
 0199        return lastDot >= 0 ? fullyQualifiedName.Substring(0, lastDot) : string.Empty;
 200    }
 201}