< Summary

Information
Class: NexusLabs.Needlr.Generators.CodeGen.ConstructorGuardCodeGenerator
Assembly: NexusLabs.Needlr.Generators
File(s): /_work/nick-alienware-needlr-1-1784953859-3e8997/needlr/needlr/src/NexusLabs.Needlr.Generators/CodeGen/ConstructorGuardCodeGenerator.cs
Line coverage
98%
Covered lines: 106
Uncovered lines: 2
Coverable lines: 108
Total lines: 223
Line coverage: 98.1%
Branch coverage
96%
Covered branches: 49
Total branches: 51
Branch coverage: 96%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
ComposeEffectiveGuards(...)100%1212100%
HasBuiltInNullRejectingGuard(...)100%44100%
BuildGuardCall(...)80%5591.66%
WriteExceptionDocumentation(...)100%2424100%
BuildCustomGuardCall(...)100%22100%
BuildParameterCondition(...)100%11100%
FormatParameterReferences(...)75%4488.88%
WriteExceptionElement(...)100%11100%

File(s)

/_work/nick-alienware-needlr-1-1784953859-3e8997/needlr/needlr/src/NexusLabs.Needlr.Generators/CodeGen/ConstructorGuardCodeGenerator.cs

#LineLine coverage
 1using System.Collections.Generic;
 2using System.Linq;
 3using System.Text;
 4
 5using NexusLabs.Needlr.Generators.Models;
 6
 7namespace NexusLabs.Needlr.Generators.CodeGen;
 8
 9/// <summary>
 10/// Composes normalized constructor guards, emits their direct calls, and writes XML
 11/// exception documentation for built-in guards.
 12/// </summary>
 13internal static class ConstructorGuardCodeGenerator
 14{
 15    /// <summary>
 16    /// Composes an optional default null guard with explicit guards, preserving order
 17    /// and removing duplicate effective calls.
 18    /// </summary>
 19    internal static ConstructorGuardModel[] ComposeEffectiveGuards(
 20        bool includeDefaultNotNull,
 21        ConstructorGuardModel[] explicitGuards)
 22    {
 9923        var suppressDefault = explicitGuards.Any(
 15124            guard => guard.Kind == GeneratedConstructorGuardKind.None);
 9925        var seen = new HashSet<(
 9926            GeneratedConstructorGuardKind Kind,
 9927            string? Type,
 9928            string? Method,
 9929            string ForwardedArguments)>();
 9930        var guards = new List<ConstructorGuardModel>();
 31
 9932        if (includeDefaultNotNull && !suppressDefault &&
 9933            seen.Add((GeneratedConstructorGuardKind.NotNull, null, null, string.Empty)))
 34        {
 335            guards.Add(new ConstructorGuardModel(
 336                GeneratedConstructorGuardKind.NotNull,
 337                null,
 338                null,
 339                null));
 40        }
 41
 30442        foreach (var guard in explicitGuards)
 43        {
 5344            if (guard.Kind == GeneratedConstructorGuardKind.None)
 45                continue;
 46
 5147            var key = (
 5148                guard.Kind,
 5149                guard.CustomGuardTypeName,
 5150                guard.CustomGuardMethodName,
 5151                ForwardedArguments: string.Join("\u0001", guard.ForwardedArgumentLiterals));
 5152            if (!seen.Add(key))
 53                continue;
 54
 4755            guards.Add(guard);
 56        }
 57
 9958        return guards.ToArray();
 59    }
 60
 61    /// <summary>
 62    /// Returns whether an effective built-in guard rejects a runtime null value.
 63    /// </summary>
 64    internal static bool HasBuiltInNullRejectingGuard(
 65        IReadOnlyList<ConstructorGuardModel> effectiveGuards)
 66    {
 1267        return effectiveGuards.Any(guard =>
 1968            guard.Kind == GeneratedConstructorGuardKind.NotNull ||
 1969            guard.Kind == GeneratedConstructorGuardKind.NotNullOrEmpty ||
 1970            guard.Kind == GeneratedConstructorGuardKind.NotNullOrWhiteSpace);
 71    }
 72
 73    /// <summary>
 74    /// Builds the direct C# call for one normalized guard.
 75    /// </summary>
 76    internal static string BuildGuardCall(
 77        ConstructorGuardModel guard,
 78        string parameterName)
 79    {
 5080        return guard.Kind switch
 5081        {
 5082            GeneratedConstructorGuardKind.NotNull =>
 683                $"global::System.ArgumentNullException.ThrowIfNull({parameterName});",
 5084            GeneratedConstructorGuardKind.NotNullOrEmpty =>
 285                $"global::System.ArgumentException.ThrowIfNullOrEmpty({parameterName});",
 5086            GeneratedConstructorGuardKind.NotNullOrWhiteSpace =>
 887                $"global::System.ArgumentException.ThrowIfNullOrWhiteSpace({parameterName});",
 5088            GeneratedConstructorGuardKind.Custom =>
 3489                BuildCustomGuardCall(guard, parameterName),
 090            _ => string.Empty,
 5091        };
 92    }
 93
 94    /// <summary>
 95    /// Writes coalesced XML exception documentation for built-in guards.
 96    /// </summary>
 97    internal static void WriteExceptionDocumentation(
 98        StringBuilder builder,
 99        IReadOnlyList<string> parameterNames,
 100        IReadOnlyList<ConstructorGuardModel[]> effectiveGuards,
 101        string indentation)
 102    {
 82103        var nullParameters = new List<string>();
 82104        var emptyParameters = new List<string>();
 82105        var whiteSpaceParameters = new List<string>();
 106
 354107        for (var i = 0; i < parameterNames.Count; i++)
 108        {
 95109            var documentsNullFailure = false;
 95110            var documentsEmptyFailure = false;
 95111            var documentsWhiteSpaceFailure = false;
 112
 290113            foreach (var guard in effectiveGuards[i])
 114            {
 50115                switch (guard.Kind)
 116                {
 117                    case GeneratedConstructorGuardKind.NotNull:
 6118                        documentsNullFailure = true;
 6119                        break;
 120                    case GeneratedConstructorGuardKind.NotNullOrEmpty:
 2121                        documentsNullFailure = true;
 2122                        documentsEmptyFailure = true;
 2123                        break;
 124                    case GeneratedConstructorGuardKind.NotNullOrWhiteSpace:
 8125                        documentsNullFailure = true;
 8126                        documentsWhiteSpaceFailure = true;
 127                        break;
 128                }
 129            }
 130
 95131            if (documentsNullFailure)
 14132                nullParameters.Add(parameterNames[i]);
 133
 95134            if (documentsWhiteSpaceFailure)
 8135                whiteSpaceParameters.Add(parameterNames[i]);
 87136            else if (documentsEmptyFailure)
 2137                emptyParameters.Add(parameterNames[i]);
 138        }
 139
 82140        if (nullParameters.Count > 0)
 141        {
 12142            var description = BuildParameterCondition(
 12143                nullParameters,
 12144                "is <see langword=\"null\"/>.");
 12145            WriteExceptionElement(
 12146                builder,
 12147                indentation,
 12148                "global::System.ArgumentNullException",
 12149                description);
 150        }
 151
 82152        if (emptyParameters.Count == 0 && whiteSpaceParameters.Count == 0)
 73153            return;
 154
 9155        var descriptions = new List<string>();
 9156        if (emptyParameters.Count > 0)
 157        {
 2158            descriptions.Add(BuildParameterCondition(
 2159                emptyParameters,
 2160                "is empty"));
 161        }
 162
 9163        if (whiteSpaceParameters.Count > 0)
 164        {
 8165            descriptions.Add(BuildParameterCondition(
 8166                whiteSpaceParameters,
 8167                "is empty or consists only of white-space characters"));
 168        }
 169
 9170        WriteExceptionElement(
 9171            builder,
 9172            indentation,
 9173            "global::System.ArgumentException",
 9174            string.Join("; or ", descriptions) + ".");
 9175    }
 176
 177    private static string BuildCustomGuardCall(
 178        ConstructorGuardModel guard,
 179        string parameterName)
 180    {
 34181        var argumentList = guard.ForwardedArgumentLiterals.Length == 0
 34182            ? parameterName
 34183            : $"{parameterName}, {string.Join(", ", guard.ForwardedArgumentLiterals)}";
 184
 34185        return $"{guard.CustomGuardTypeName}.{guard.CustomGuardMethodName}({argumentList}, nameof({parameterName}));";
 186    }
 187
 188    private static string BuildParameterCondition(
 189        IReadOnlyList<string> parameterNames,
 190        string condition)
 191    {
 22192        return $"{FormatParameterReferences(parameterNames)} {condition}";
 193    }
 194
 195    private static string FormatParameterReferences(IReadOnlyList<string> parameterNames)
 196    {
 22197        if (parameterNames.Count == 1)
 21198            return $"<paramref name=\"{parameterNames[0]}\"/>";
 199
 1200        if (parameterNames.Count == 2)
 201        {
 0202            return $"<paramref name=\"{parameterNames[0]}\"/> or <paramref name=\"{parameterNames[1]}\"/>";
 203        }
 204
 1205        var references = parameterNames
 3206            .Select(parameterName => $"<paramref name=\"{parameterName}\"/>")
 1207            .ToArray();
 1208        return string.Join(", ", references.Take(references.Length - 1)) +
 1209            $", or {references[references.Length - 1]}";
 210    }
 211
 212    private static void WriteExceptionElement(
 213        StringBuilder builder,
 214        string indentation,
 215        string exceptionTypeName,
 216        string description)
 217    {
 21218        builder.AppendLine(
 21219            $"{indentation}/// <exception cref=\"{exceptionTypeName}\">");
 21220        builder.AppendLine($"{indentation}/// {description}");
 21221        builder.AppendLine($"{indentation}/// </exception>");
 21222    }
 223}