< Summary

Information
Class: NexusLabs.Needlr.Generators.CodeGen.ConstructorGuardCodeGenerator
Assembly: NexusLabs.Needlr.Generators
File(s): /home/runner/work/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)

/home/runner/work/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    {
 17623        var suppressDefault = explicitGuards.Any(
 24224            guard => guard.Kind == GeneratedConstructorGuardKind.None);
 17625        var seen = new HashSet<(
 17626            GeneratedConstructorGuardKind Kind,
 17627            string? Type,
 17628            string? Method,
 17629            string ForwardedArguments)>();
 17630        var guards = new List<ConstructorGuardModel>();
 31
 17632        if (includeDefaultNotNull && !suppressDefault &&
 17633            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
 48642        foreach (var guard in explicitGuards)
 43        {
 6744            if (guard.Kind == GeneratedConstructorGuardKind.None)
 45                continue;
 46
 6547            var key = (
 6548                guard.Kind,
 6549                guard.CustomGuardTypeName,
 6550                guard.CustomGuardMethodName,
 6551                ForwardedArguments: string.Join("\u0001", guard.ForwardedArgumentLiterals));
 6552            if (!seen.Add(key))
 53                continue;
 54
 6155            guards.Add(guard);
 56        }
 57
 17658        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    {
 1867        return effectiveGuards.Any(guard =>
 2768            guard.Kind == GeneratedConstructorGuardKind.NotNull ||
 2769            guard.Kind == GeneratedConstructorGuardKind.NotNullOrEmpty ||
 2770            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    {
 6480        return guard.Kind switch
 6481        {
 6482            GeneratedConstructorGuardKind.NotNull =>
 783                $"global::System.ArgumentNullException.ThrowIfNull({parameterName});",
 6484            GeneratedConstructorGuardKind.NotNullOrEmpty =>
 485                $"global::System.ArgumentException.ThrowIfNullOrEmpty({parameterName});",
 6486            GeneratedConstructorGuardKind.NotNullOrWhiteSpace =>
 987                $"global::System.ArgumentException.ThrowIfNullOrWhiteSpace({parameterName});",
 6488            GeneratedConstructorGuardKind.Custom =>
 4489                BuildCustomGuardCall(guard, parameterName),
 090            _ => string.Empty,
 6491        };
 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    {
 140103        var nullParameters = new List<string>();
 140104        var emptyParameters = new List<string>();
 140105        var whiteSpaceParameters = new List<string>();
 106
 602107        for (var i = 0; i < parameterNames.Count; i++)
 108        {
 161109            var documentsNullFailure = false;
 161110            var documentsEmptyFailure = false;
 161111            var documentsWhiteSpaceFailure = false;
 112
 450113            foreach (var guard in effectiveGuards[i])
 114            {
 64115                switch (guard.Kind)
 116                {
 117                    case GeneratedConstructorGuardKind.NotNull:
 7118                        documentsNullFailure = true;
 7119                        break;
 120                    case GeneratedConstructorGuardKind.NotNullOrEmpty:
 4121                        documentsNullFailure = true;
 4122                        documentsEmptyFailure = true;
 4123                        break;
 124                    case GeneratedConstructorGuardKind.NotNullOrWhiteSpace:
 9125                        documentsNullFailure = true;
 9126                        documentsWhiteSpaceFailure = true;
 127                        break;
 128                }
 129            }
 130
 161131            if (documentsNullFailure)
 18132                nullParameters.Add(parameterNames[i]);
 133
 161134            if (documentsWhiteSpaceFailure)
 9135                whiteSpaceParameters.Add(parameterNames[i]);
 152136            else if (documentsEmptyFailure)
 4137                emptyParameters.Add(parameterNames[i]);
 138        }
 139
 140140        if (nullParameters.Count > 0)
 141        {
 16142            var description = BuildParameterCondition(
 16143                nullParameters,
 16144                "is <see langword=\"null\"/>.");
 16145            WriteExceptionElement(
 16146                builder,
 16147                indentation,
 16148                "global::System.ArgumentNullException",
 16149                description);
 150        }
 151
 140152        if (emptyParameters.Count == 0 && whiteSpaceParameters.Count == 0)
 128153            return;
 154
 12155        var descriptions = new List<string>();
 12156        if (emptyParameters.Count > 0)
 157        {
 4158            descriptions.Add(BuildParameterCondition(
 4159                emptyParameters,
 4160                "is empty"));
 161        }
 162
 12163        if (whiteSpaceParameters.Count > 0)
 164        {
 9165            descriptions.Add(BuildParameterCondition(
 9166                whiteSpaceParameters,
 9167                "is empty or consists only of white-space characters"));
 168        }
 169
 12170        WriteExceptionElement(
 12171            builder,
 12172            indentation,
 12173            "global::System.ArgumentException",
 12174            string.Join("; or ", descriptions) + ".");
 12175    }
 176
 177    private static string BuildCustomGuardCall(
 178        ConstructorGuardModel guard,
 179        string parameterName)
 180    {
 44181        var argumentList = guard.ForwardedArgumentLiterals.Length == 0
 44182            ? parameterName
 44183            : $"{parameterName}, {string.Join(", ", guard.ForwardedArgumentLiterals)}";
 184
 44185        return $"{guard.CustomGuardTypeName}.{guard.CustomGuardMethodName}({argumentList}, nameof({parameterName}));";
 186    }
 187
 188    private static string BuildParameterCondition(
 189        IReadOnlyList<string> parameterNames,
 190        string condition)
 191    {
 29192        return $"{FormatParameterReferences(parameterNames)} {condition}";
 193    }
 194
 195    private static string FormatParameterReferences(IReadOnlyList<string> parameterNames)
 196    {
 29197        if (parameterNames.Count == 1)
 28198            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    {
 28218        builder.AppendLine(
 28219            $"{indentation}/// <exception cref=\"{exceptionTypeName}\">");
 28220        builder.AppendLine($"{indentation}/// {description}");
 28221        builder.AppendLine($"{indentation}/// </exception>");
 28222    }
 223}