< Summary

Information
Class: NexusLabs.Needlr.Generators.ConstructorGuardDiscoveryHelper
Assembly: NexusLabs.Needlr.Generators
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators/ConstructorGuardDiscoveryHelper.cs
Line coverage
97%
Covered lines: 66
Uncovered lines: 2
Coverable lines: 68
Total lines: 161
Line coverage: 97%
Branch coverage
91%
Covered branches: 42
Total branches: 46
Branch coverage: 91.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
GetExplicitGuards(...)100%1212100%
IsConstructorGuardAttributeClass(...)100%11100%
TryGetGuardDefinition(...)85.71%1414100%
TryRenderForwardedArguments(...)100%88100%
ParseConstructorGuardAttribute(...)83.33%121290%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators/ConstructorGuardDiscoveryHelper.cs

#LineLine coverage
 1using System.Collections.Generic;
 2
 3using Microsoft.CodeAnalysis;
 4
 5using NexusLabs.Needlr.Generators.Models;
 6using NexusLabs.Needlr.Roslyn.Shared;
 7
 8namespace NexusLabs.Needlr.Generators;
 9
 10/// <summary>
 11/// Discovers and normalizes direct constructor guards and custom guard aliases on a
 12/// field or property.
 13/// </summary>
 14internal static class ConstructorGuardDiscoveryHelper
 15{
 16    private const string ConstructorGuardAttributeName = "ConstructorGuardAttribute";
 17    private const string ConstructorGuardDefinitionAttributeName = "ConstructorGuardDefinitionAttribute";
 18    private const string DefaultGuardMethodName = "Validate";
 19
 20    /// <summary>
 21    /// Returns every direct or aliased constructor guard on
 22    /// <paramref name="member"/>, preserving attribute declaration order.
 23    /// </summary>
 24    internal static ConstructorGuardModel[] GetExplicitGuards(ISymbol member)
 25    {
 42726        var guards = new List<ConstructorGuardModel>();
 27
 118028        foreach (var attribute in member.GetAttributes())
 29        {
 16330            var attrClass = attribute.AttributeClass;
 16331            if (attrClass is null)
 32                continue;
 33
 16334            if (IsConstructorGuardAttributeClass(attrClass))
 35            {
 5136                var parsed = ParseConstructorGuardAttribute(attribute);
 5137                if (parsed.HasValue)
 5138                    guards.Add(parsed.Value);
 39
 5140                continue;
 41            }
 42
 11243            if (!TryGetGuardDefinition(attrClass, out var guardTypeName, out var guardMethodName))
 44                continue;
 45
 3246            var forwardedArgumentLiterals = TryRenderForwardedArguments(attribute);
 3247            if (forwardedArgumentLiterals is null)
 48            {
 49                // Unsupported arguments invalidate the complete guard call so the
 50                // generator never emits a partial or positionally incorrect call.
 51                continue;
 52            }
 53
 2954            guards.Add(new ConstructorGuardModel(
 2955                GeneratedConstructorGuardKind.Custom,
 2956                guardTypeName,
 2957                guardMethodName,
 2958                forwardedArgumentLiterals));
 59        }
 60
 42761        return guards.ToArray();
 62    }
 63
 64    /// <summary>
 65    /// Returns whether <paramref name="attributeClass"/> is Needlr's direct
 66    /// <c>ConstructorGuardAttribute</c>.
 67    /// </summary>
 68    internal static bool IsConstructorGuardAttributeClass(INamedTypeSymbol attributeClass)
 69    {
 25370        return GeneratedConstructorEligibility.IsNeedlrGeneratorsAttribute(
 25371            attributeClass,
 25372            ConstructorGuardAttributeName);
 73    }
 74
 75    /// <summary>
 76    /// Resolves a custom guard alias definition from an attribute type.
 77    /// </summary>
 78    internal static bool TryGetGuardDefinition(
 79        INamedTypeSymbol attributeClass,
 80        out string guardTypeName,
 81        out string guardMethodName)
 82    {
 41483        foreach (var metaAttribute in attributeClass.GetAttributes())
 84        {
 11185            var metaClass = metaAttribute.AttributeClass;
 11186            if (metaClass is null ||
 11187                !GeneratedConstructorEligibility.IsNeedlrGeneratorsAttribute(
 11188                    metaClass,
 11189                    ConstructorGuardDefinitionAttributeName))
 90            {
 91                continue;
 92            }
 93
 3294            if (metaAttribute.ConstructorArguments.Length == 0 ||
 3295                metaAttribute.ConstructorArguments[0].Value is not ITypeSymbol guardType)
 96            {
 97                continue;
 98            }
 99
 32100            guardTypeName = guardType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 32101            guardMethodName = metaAttribute.ConstructorArguments.Length > 1 &&
 32102                metaAttribute.ConstructorArguments[1].Value is string explicitName
 32103                    ? explicitName
 32104                    : DefaultGuardMethodName;
 32105            return true;
 106        }
 107
 80108        guardTypeName = string.Empty;
 80109        guardMethodName = string.Empty;
 80110        return false;
 111    }
 112
 113    private static string[]? TryRenderForwardedArguments(AttributeData attribute)
 114    {
 32115        if (attribute.NamedArguments.Length > 0)
 1116            return null;
 117
 31118        if (attribute.ConstructorArguments.Length == 0)
 4119            return System.Array.Empty<string>();
 120
 27121        var rendered = new string[attribute.ConstructorArguments.Length];
 120122        for (var i = 0; i < attribute.ConstructorArguments.Length; i++)
 123        {
 35124            if (!TypedConstantRenderer.TryRender(attribute.ConstructorArguments[i], out var literal))
 2125                return null;
 126
 33127            rendered[i] = literal;
 128        }
 129
 25130        return rendered;
 131    }
 132
 133    private static ConstructorGuardModel? ParseConstructorGuardAttribute(AttributeData attribute)
 134    {
 51135        if (attribute.ConstructorArguments.Length == 0)
 0136            return null;
 137
 51138        var first = attribute.ConstructorArguments[0];
 139
 51140        if (first.Kind == TypedConstantKind.Enum && first.Value is int enumValue)
 32141            return new ConstructorGuardModel(
 32142                (GeneratedConstructorGuardKind)enumValue,
 32143                null,
 32144                null,
 32145                null);
 146
 19147        if (first.Value is not ITypeSymbol guardType)
 0148            return null;
 149
 19150        var methodName = attribute.ConstructorArguments.Length > 1 &&
 19151            attribute.ConstructorArguments[1].Value is string explicitName
 19152                ? explicitName
 19153                : DefaultGuardMethodName;
 154
 19155        return new ConstructorGuardModel(
 19156            GeneratedConstructorGuardKind.Custom,
 19157            guardType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
 19158            methodName,
 19159            null);
 160    }
 161}