< Summary

Information
Class: NexusLabs.Needlr.Generators.ConstructorGuardDiscoveryHelper
Assembly: NexusLabs.Needlr.Generators
File(s): /_work/nick-alienware-needlr-1-1784953859-3e8997/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)

/_work/nick-alienware-needlr-1-1784953859-3e8997/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    {
 34426        var guards = new List<ConstructorGuardModel>();
 27
 88028        foreach (var attribute in member.GetAttributes())
 29        {
 9630            var attrClass = attribute.AttributeClass;
 9631            if (attrClass is null)
 32                continue;
 33
 9634            if (IsConstructorGuardAttributeClass(attrClass))
 35            {
 4136                var parsed = ParseConstructorGuardAttribute(attribute);
 4137                if (parsed.HasValue)
 4138                    guards.Add(parsed.Value);
 39
 4140                continue;
 41            }
 42
 5543            if (!TryGetGuardDefinition(attrClass, out var guardTypeName, out var guardMethodName))
 44                continue;
 45
 2846            var forwardedArgumentLiterals = TryRenderForwardedArguments(attribute);
 2847            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
 2554            guards.Add(new ConstructorGuardModel(
 2555                GeneratedConstructorGuardKind.Custom,
 2556                guardTypeName,
 2557                guardMethodName,
 2558                forwardedArgumentLiterals));
 59        }
 60
 34461        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    {
 12770        return GeneratedConstructorEligibility.IsNeedlrGeneratorsAttribute(
 12771            attributeClass,
 12772            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    {
 19083        foreach (var metaAttribute in attributeClass.GetAttributes())
 84        {
 5485            var metaClass = metaAttribute.AttributeClass;
 5486            if (metaClass is null ||
 5487                !GeneratedConstructorEligibility.IsNeedlrGeneratorsAttribute(
 5488                    metaClass,
 5489                    ConstructorGuardDefinitionAttributeName))
 90            {
 91                continue;
 92            }
 93
 2894            if (metaAttribute.ConstructorArguments.Length == 0 ||
 2895                metaAttribute.ConstructorArguments[0].Value is not ITypeSymbol guardType)
 96            {
 97                continue;
 98            }
 99
 28100            guardTypeName = guardType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 28101            guardMethodName = metaAttribute.ConstructorArguments.Length > 1 &&
 28102                metaAttribute.ConstructorArguments[1].Value is string explicitName
 28103                    ? explicitName
 28104                    : DefaultGuardMethodName;
 28105            return true;
 106        }
 107
 27108        guardTypeName = string.Empty;
 27109        guardMethodName = string.Empty;
 27110        return false;
 111    }
 112
 113    private static string[]? TryRenderForwardedArguments(AttributeData attribute)
 114    {
 28115        if (attribute.NamedArguments.Length > 0)
 1116            return null;
 117
 27118        if (attribute.ConstructorArguments.Length == 0)
 4119            return System.Array.Empty<string>();
 120
 23121        var rendered = new string[attribute.ConstructorArguments.Length];
 104122        for (var i = 0; i < attribute.ConstructorArguments.Length; i++)
 123        {
 31124            if (!TypedConstantRenderer.TryRender(attribute.ConstructorArguments[i], out var literal))
 2125                return null;
 126
 29127            rendered[i] = literal;
 128        }
 129
 21130        return rendered;
 131    }
 132
 133    private static ConstructorGuardModel? ParseConstructorGuardAttribute(AttributeData attribute)
 134    {
 41135        if (attribute.ConstructorArguments.Length == 0)
 0136            return null;
 137
 41138        var first = attribute.ConstructorArguments[0];
 139
 41140        if (first.Kind == TypedConstantKind.Enum && first.Value is int enumValue)
 28141            return new ConstructorGuardModel(
 28142                (GeneratedConstructorGuardKind)enumValue,
 28143                null,
 28144                null,
 28145                null);
 146
 13147        if (first.Value is not ITypeSymbol guardType)
 0148            return null;
 149
 13150        var methodName = attribute.ConstructorArguments.Length > 1 &&
 13151            attribute.ConstructorArguments[1].Value is string explicitName
 13152                ? explicitName
 13153                : DefaultGuardMethodName;
 154
 13155        return new ConstructorGuardModel(
 13156            GeneratedConstructorGuardKind.Custom,
 13157            guardType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
 13158            methodName,
 13159            null);
 160    }
 161}