< Summary

Information
Class: NexusLabs.Needlr.Generators.OptionsAttributeHelper
Assembly: NexusLabs.Needlr.Generators
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators/OptionsAttributeHelper.cs
Line coverage
98%
Covered lines: 61
Uncovered lines: 1
Coverable lines: 62
Total lines: 188
Line coverage: 98.3%
Branch coverage
96%
Covered branches: 54
Total branches: 56
Branch coverage: 96.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_SectionName()100%11100%
get_Name()100%11100%
get_ValidateOnStart()100%11100%
get_ValidateMethod()100%11100%
get_ValidatorType()100%11100%
HasOptionsAttribute(...)87.5%8890%
GetOptionsAttributes(...)96.66%3030100%
FindValidationMethod(...)100%1818100%
.ctor(...)100%11100%
get_MethodName()100%11100%
get_IsStatic()100%11100%

File(s)

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

#LineLine coverage
 1using Microsoft.CodeAnalysis;
 2
 3namespace NexusLabs.Needlr.Generators;
 4
 5/// <summary>
 6/// Helper for discovering Options attributes from Roslyn symbols.
 7/// </summary>
 8internal static class OptionsAttributeHelper
 9{
 10    private const string OptionsAttributeName = "OptionsAttribute";
 11    private const string OptionsAttributeFullName = "NexusLabs.Needlr.OptionsAttribute";
 12
 13    /// <summary>
 14    /// Information extracted from an [Options] attribute.
 15    /// </summary>
 16    public readonly struct OptionsAttributeInfo
 17    {
 18        public OptionsAttributeInfo(string? sectionName, string? name, bool validateOnStart, string? validateMethod = nu
 19        {
 17320            SectionName = sectionName;
 17321            Name = name;
 17322            ValidateOnStart = validateOnStart;
 17323            ValidateMethod = validateMethod;
 17324            ValidatorType = validatorType;
 17325        }
 26
 27        /// <summary>Explicit section name from attribute, or null to infer from class name.</summary>
 17328        public string? SectionName { get; }
 29
 30        /// <summary>Named options name (e.g., "Primary"), or null for default options.</summary>
 17331        public string? Name { get; }
 32
 33        /// <summary>Whether to validate options on startup.</summary>
 17334        public bool ValidateOnStart { get; }
 35
 36        /// <summary>Custom validation method name, or null to use convention ("Validate").</summary>
 34637        public string? ValidateMethod { get; }
 38
 39        /// <summary>External validator type, or null to use the options class itself.</summary>
 17340        public INamedTypeSymbol? ValidatorType { get; }
 41    }
 42
 43    /// <summary>
 44    /// Checks if a type has the [Options] attribute.
 45    /// </summary>
 46    /// <param name="typeSymbol">The type symbol to check.</param>
 47    /// <returns>True if the type has [Options]; otherwise, false.</returns>
 48    public static bool HasOptionsAttribute(INamedTypeSymbol typeSymbol)
 49    {
 682809950        foreach (var attribute in typeSymbol.GetAttributes())
 51        {
 199385352            var attributeClass = attribute.AttributeClass;
 199385353            if (attributeClass == null)
 54                continue;
 55
 199385356            var name = attributeClass.Name;
 199385357            if (name == OptionsAttributeName)
 16758                return true;
 59
 199368660            var fullName = attributeClass.ToDisplayString();
 199368661            if (fullName == OptionsAttributeFullName)
 062                return true;
 63        }
 64
 142011365        return false;
 66    }
 67
 68    /// <summary>
 69    /// Gets all [Options] attribute data from a type.
 70    /// </summary>
 71    /// <param name="typeSymbol">The type symbol to check.</param>
 72    /// <returns>A list of options attribute info for each [Options] on the type.</returns>
 73    public static IReadOnlyList<OptionsAttributeInfo> GetOptionsAttributes(INamedTypeSymbol typeSymbol)
 74    {
 16775        var result = new List<OptionsAttributeInfo>();
 76
 68077        foreach (var attribute in typeSymbol.GetAttributes())
 78        {
 17379            var attributeClass = attribute.AttributeClass;
 17380            if (attributeClass == null)
 81                continue;
 82
 17383            var name = attributeClass.Name;
 17384            var fullName = attributeClass.ToDisplayString();
 85
 17386            if (name != OptionsAttributeName && fullName != OptionsAttributeFullName)
 87                continue;
 88
 89            // Extract constructor argument (optional section name)
 17390            string? sectionName = null;
 17391            if (attribute.ConstructorArguments.Length > 0 &&
 17392                attribute.ConstructorArguments[0].Value is string section)
 93            {
 8694                sectionName = section;
 95            }
 96
 97            // Extract named arguments
 17398            string? optionsName = null;
 17399            bool validateOnStart = false;
 173100            string? validateMethod = null;
 173101            INamedTypeSymbol? validatorType = null;
 102
 502103            foreach (var namedArg in attribute.NamedArguments)
 104            {
 78105                if (namedArg.Key == "Name" && namedArg.Value.Value is string n)
 106                {
 22107                    optionsName = n;
 108                }
 56109                else if (namedArg.Key == "ValidateOnStart" && namedArg.Value.Value is bool v)
 110                {
 46111                    validateOnStart = v;
 112                }
 10113                else if (namedArg.Key == "ValidateMethod" && namedArg.Value.Value is string vm)
 114                {
 3115                    validateMethod = vm;
 116                }
 7117                else if (namedArg.Key == "Validator" && namedArg.Value.Value is INamedTypeSymbol vt)
 118                {
 6119                    validatorType = vt;
 120                }
 121            }
 122
 173123            result.Add(new OptionsAttributeInfo(sectionName, optionsName, validateOnStart, validateMethod, validatorType
 124        }
 125
 167126        return result;
 127    }
 128
 129    /// <summary>
 130    /// Finds a validation method on a type by convention or explicit name.
 131    /// Convention: method named "Validate" (or custom name via ValidateMethod property).
 132    /// </summary>
 133    /// <param name="typeSymbol">The type symbol to search.</param>
 134    /// <param name="methodName">The method name to look for (default: "Validate").</param>
 135    /// <returns>Validator method info, or null if no validator method found.</returns>
 136    public static OptionsValidatorMethodInfo? FindValidationMethod(INamedTypeSymbol typeSymbol, string methodName = "Val
 137    {
 138        // Look for method by name (convention-based)
 2948139        foreach (var member in typeSymbol.GetMembers())
 140        {
 1310141            if (member is not IMethodSymbol method)
 142                continue;
 143
 800144            if (method.Name != methodName)
 145                continue;
 146
 147            // Check signature: should return IEnumerable<string> or IEnumerable<ValidationError>
 148            // Supported signatures:
 149            // 1. Instance method with no parameters: T.Validate() - for self-validation
 150            // 2. Static method with one parameter: static T.Validate(TOptions options)
 151            // 3. Instance method with one parameter: validator.Validate(TOptions options) - for external validators
 18152            if (method.Parameters.Length == 0 && !method.IsStatic)
 153            {
 154                // Instance method: T.Validate() - self-validation on options class
 11155                return new OptionsValidatorMethodInfo(method.Name, false);
 156            }
 157
 7158            if (method.Parameters.Length == 1 && method.IsStatic)
 159            {
 160                // Static method: T.Validate(T options) - static validator
 3161                return new OptionsValidatorMethodInfo(method.Name, true);
 162            }
 163
 4164            if (method.Parameters.Length == 1 && !method.IsStatic)
 165            {
 166                // Instance method with parameter: validator.Validate(T options) - external validator
 4167                return new OptionsValidatorMethodInfo(method.Name, false);
 168            }
 169        }
 170
 155171        return null;
 172    }
 173
 174    /// <summary>
 175    /// Information about a validation method.
 176    /// </summary>
 177    public readonly struct OptionsValidatorMethodInfo
 178    {
 179        public OptionsValidatorMethodInfo(string methodName, bool isStatic)
 180        {
 18181            MethodName = methodName;
 18182            IsStatic = isStatic;
 18183        }
 184
 18185        public string MethodName { get; }
 18186        public bool IsStatic { get; }
 187    }
 188}