< 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
90%
Covered lines: 131
Uncovered lines: 13
Coverable lines: 144
Total lines: 251
Line coverage: 90.9%
Branch coverage
73%
Covered branches: 22
Total branches: 30
Branch coverage: 73.3%
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%
GenerateProvidersSource(...)100%22100%
GenerateShorthandProviderSource(...)100%11100%

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.Collections.Generic;
 5using System.Linq;
 6using System.Text;
 7using NexusLabs.Needlr.Generators.Models;
 8
 9namespace NexusLabs.Needlr.Generators.CodeGen;
 10
 11/// <summary>
 12/// Generates Provider classes for [Provider] attributed types.
 13/// </summary>
 14internal static class ProviderCodeGenerator
 15{
 16    /// <summary>
 17    /// Generates provider implementation for an interface-based provider.
 18    /// </summary>
 19    internal static void GenerateProviderImplementation(StringBuilder builder, DiscoveredProvider provider, string gener
 20    {
 1221        var implName = provider.ImplementationTypeName;
 22
 1223        builder.AppendLine("/// <summary>");
 1224        builder.AppendLine($"/// Strongly-typed service provider implementing <see cref=\"{provider.TypeName}\"/>.");
 1225        builder.AppendLine("/// </summary>");
 1226        builder.AppendLine("/// <remarks>");
 1227        builder.AppendLine("/// <para>");
 1228        builder.AppendLine("/// This provider is registered as a <b>Singleton</b>. All service properties are resolved")
 1229        builder.AppendLine("/// at construction time via constructor injection for fail-fast error detection.");
 1230        builder.AppendLine("/// </para>");
 1231        builder.AppendLine("/// <para>");
 1232        builder.AppendLine("/// To create new instances on demand, use factory properties instead of direct service refe
 1233        builder.AppendLine("/// </para>");
 1234        builder.AppendLine("/// </remarks>");
 1235        builder.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generators\", \"1
 1236        builder.AppendLine($"public sealed class {implName} : {provider.TypeName}");
 1237        builder.AppendLine("{");
 38
 39        // Generate constructor with all properties (Required, Optional, Collection, Factory)
 40        // Optional parameters must come last in C#
 1241        var injectableProps = provider.Properties
 542            .OrderBy(p => p.Kind == ProviderPropertyKind.Optional ? 1 : 0)
 1243            .ToList();
 44
 1245        var ctorParams = injectableProps.Select(p =>
 1246        {
 1547            var paramName = GeneratorHelpers.ToCamelCase(p.PropertyName);
 1548            if (p.Kind == ProviderPropertyKind.Optional)
 1249            {
 250                return $"{p.ServiceTypeName}? {paramName} = null";
 1251            }
 1352            return $"{p.ServiceTypeName} {paramName}";
 1253        });
 54
 1255        var ctorParamList = string.Join(", ", ctorParams);
 56
 1257        builder.AppendLine($"    /// <summary>");
 1258        builder.AppendLine($"    /// Creates a new instance of <see cref=\"{implName}\"/>.");
 1259        builder.AppendLine($"    /// </summary>");
 1260        builder.AppendLine($"    public {implName}({ctorParamList})");
 1261        builder.AppendLine("    {");
 62
 5463        foreach (var prop in injectableProps)
 64        {
 1565            var paramName = GeneratorHelpers.ToCamelCase(prop.PropertyName);
 1566            builder.AppendLine($"        {prop.PropertyName} = {paramName};");
 67        }
 68
 1269        builder.AppendLine("    }");
 1270        builder.AppendLine();
 71
 72        // Generate properties
 5473        foreach (var prop in provider.Properties)
 74        {
 1575            var propTypeName = prop.Kind == ProviderPropertyKind.Optional
 1576                ? $"{prop.ServiceTypeName}?"
 1577                : prop.ServiceTypeName;
 78
 1579            builder.AppendLine($"    /// <inheritdoc />");
 1580            builder.AppendLine($"    public {propTypeName} {prop.PropertyName} {{ get; }}");
 81        }
 82
 1283        builder.AppendLine("}");
 1284    }
 85
 86    /// <summary>
 87    /// Generates interface and partial class for shorthand [Provider(typeof(T))] on a class.
 88    /// </summary>
 89    internal static void GenerateProviderInterfaceAndPartialClass(StringBuilder builder, DiscoveredProvider provider, st
 90    {
 691        var interfaceName = provider.InterfaceTypeName;
 692        var className = provider.SimpleTypeName;
 93
 94        // Generate interface
 695        builder.AppendLine("/// <summary>");
 696        builder.AppendLine($"/// Interface for strongly-typed service provider generated from <see cref=\"{provider.Type
 697        builder.AppendLine("/// </summary>");
 698        builder.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generators\", \"1
 699        builder.AppendLine($"public interface {interfaceName}");
 6100        builder.AppendLine("{");
 101
 26102        foreach (var prop in provider.Properties)
 103        {
 7104            var propTypeName = prop.Kind == ProviderPropertyKind.Optional
 7105                ? $"{prop.ServiceTypeName}?"
 7106                : prop.ServiceTypeName;
 107
 7108            builder.AppendLine($"    /// <summary>Gets the {prop.PropertyName} service.</summary>");
 7109            builder.AppendLine($"    {propTypeName} {prop.PropertyName} {{ get; }}");
 110        }
 111
 6112        builder.AppendLine("}");
 6113        builder.AppendLine();
 114
 115        // Generate partial class implementation
 6116        builder.AppendLine("/// <summary>");
 6117        builder.AppendLine("/// Strongly-typed service provider.");
 6118        builder.AppendLine("/// </summary>");
 6119        builder.AppendLine("/// <remarks>");
 6120        builder.AppendLine("/// <para>");
 6121        builder.AppendLine("/// This provider is registered as a <b>Singleton</b>. All service properties are resolved")
 6122        builder.AppendLine("/// at construction time via constructor injection for fail-fast error detection.");
 6123        builder.AppendLine("/// </para>");
 6124        builder.AppendLine("/// </remarks>");
 6125        builder.AppendLine($"public partial class {className} : {interfaceName}");
 6126        builder.AppendLine("{");
 127
 128        // Generate constructor with all properties (Required, Optional, Collection, Factory)
 129        // Optional parameters must come last in C#
 6130        var injectableProps = provider.Properties
 2131            .OrderBy(p => p.Kind == ProviderPropertyKind.Optional ? 1 : 0)
 6132            .ToList();
 133
 6134        var ctorParams = injectableProps.Select(p =>
 6135        {
 7136            var paramName = GeneratorHelpers.ToCamelCase(p.PropertyName);
 7137            if (p.Kind == ProviderPropertyKind.Optional)
 6138            {
 2139                return $"{p.ServiceTypeName}? {paramName} = null";
 6140            }
 5141            return $"{p.ServiceTypeName} {paramName}";
 6142        });
 143
 6144        var ctorParamList = string.Join(", ", ctorParams);
 145
 6146        builder.AppendLine($"    /// <summary>");
 6147        builder.AppendLine($"    /// Creates a new instance of <see cref=\"{className}\"/>.");
 6148        builder.AppendLine($"    /// </summary>");
 6149        builder.AppendLine($"    public {className}({ctorParamList})");
 6150        builder.AppendLine("    {");
 151
 26152        foreach (var prop in injectableProps)
 153        {
 7154            var paramName = GeneratorHelpers.ToCamelCase(prop.PropertyName);
 7155            builder.AppendLine($"        {prop.PropertyName} = {paramName};");
 156        }
 157
 6158        builder.AppendLine("    }");
 6159        builder.AppendLine();
 160
 161        // Generate properties
 26162        foreach (var prop in provider.Properties)
 163        {
 7164            var propTypeName = prop.Kind == ProviderPropertyKind.Optional
 7165                ? $"{prop.ServiceTypeName}?"
 7166                : prop.ServiceTypeName;
 167
 7168            builder.AppendLine($"    /// <inheritdoc />");
 7169            builder.AppendLine($"    public {propTypeName} {prop.PropertyName} {{ get; }}");
 170        }
 171
 6172        builder.AppendLine("}");
 6173    }
 174
 175    /// <summary>
 176    /// Generates provider registration code for the TypeRegistry.
 177    /// </summary>
 178    internal static void GenerateProviderRegistration(StringBuilder builder, DiscoveredProvider provider, string generat
 179    {
 0180        var interfaceTypeName = provider.IsInterface
 0181            ? provider.TypeName
 0182            : $"global::{GetNamespace(provider.TypeName)}.{provider.InterfaceTypeName}";
 183
 0184        var implTypeName = provider.IsInterface
 0185            ? $"global::{generatedNamespace}.{provider.ImplementationTypeName}"
 0186            : provider.TypeName;
 187
 0188        builder.AppendLine($"            // Provider: {provider.SimpleTypeName}");
 0189        builder.AppendLine($"            services.AddSingleton<{interfaceTypeName}, {implTypeName}>();");
 0190    }
 191
 192    private static string GetNamespace(string fullyQualifiedName)
 193    {
 0194        if (fullyQualifiedName.StartsWith("global::"))
 195        {
 0196            fullyQualifiedName = fullyQualifiedName.Substring(8);
 197        }
 198
 0199        var lastDot = fullyQualifiedName.LastIndexOf('.');
 0200        return lastDot >= 0 ? fullyQualifiedName.Substring(0, lastDot) : string.Empty;
 201    }
 202
 203    /// <summary>
 204    /// Generates the complete Providers.g.cs source file for interface-based providers.
 205    /// </summary>
 206    internal static string GenerateProvidersSource(IReadOnlyList<DiscoveredProvider> providers, string assemblyName, Bre
 207    {
 11208        var builder = new StringBuilder();
 11209        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblyName);
 210
 11211        breadcrumbs.WriteFileHeader(builder, assemblyName, "Needlr Generated Providers");
 11212        builder.AppendLine("#nullable enable");
 11213        builder.AppendLine();
 11214        builder.AppendLine("using System;");
 11215        builder.AppendLine();
 11216        builder.AppendLine("using Microsoft.Extensions.DependencyInjection;");
 11217        builder.AppendLine();
 11218        builder.AppendLine($"namespace {safeAssemblyName}.Generated;");
 11219        builder.AppendLine();
 220
 221        // Generate provider implementations (interface-based only)
 46222        foreach (var provider in providers)
 223        {
 12224            GenerateProviderImplementation(builder, provider, $"{safeAssemblyName}.Generated", breadcrumbs, projectDirec
 12225            builder.AppendLine();
 226        }
 227
 11228        return builder.ToString();
 229    }
 230
 231    /// <summary>
 232    /// Generates the Provider.{TypeName}.g.cs source file for a shorthand class provider.
 233    /// </summary>
 234    internal static string GenerateShorthandProviderSource(DiscoveredProvider provider, string assemblyName, BreadcrumbW
 235    {
 6236        var builder = new StringBuilder();
 6237        var providerNamespace = GeneratorHelpers.GetNamespaceFromTypeName(provider.TypeName);
 238
 6239        breadcrumbs.WriteFileHeader(builder, assemblyName, $"Needlr Generated Provider: {provider.SimpleTypeName}");
 6240        builder.AppendLine("#nullable enable");
 6241        builder.AppendLine();
 6242        builder.AppendLine("using System;");
 6243        builder.AppendLine();
 6244        builder.AppendLine($"namespace {providerNamespace};");
 6245        builder.AppendLine();
 246
 6247        GenerateProviderInterfaceAndPartialClass(builder, provider, providerNamespace, breadcrumbs, projectDirectory);
 248
 6249        return builder.ToString();
 250    }
 251}