< Summary

Information
Class: NexusLabs.Needlr.Generators.UnsupportedDataAnnotationAnalyzer
Assembly: NexusLabs.Needlr.Generators
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators/UnsupportedDataAnnotationAnalyzer.cs
Line coverage
95%
Covered lines: 65
Uncovered lines: 3
Coverable lines: 68
Total lines: 162
Line coverage: 95.5%
Branch coverage
81%
Covered branches: 36
Total branches: 44
Branch coverage: 81.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
get_SupportedDiagnostics()100%11100%
Initialize(...)100%11100%
AnalyzeClassDeclaration(...)100%11100%
AnalyzeRecordDeclaration(...)100%11100%
AnalyzeTypeDeclaration(...)87.5%8888.88%
HasOptionsAttribute(...)91.66%1212100%
AnalyzeProperty(...)75%202095%
IsValidationAttribute(...)75%4485.71%

File(s)

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

#LineLine coverage
 1// Copyright (c) NexusLabs. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System.Collections.Generic;
 5using System.Collections.Immutable;
 6using System.Linq;
 7
 8using Microsoft.CodeAnalysis;
 9using Microsoft.CodeAnalysis.CSharp;
 10using Microsoft.CodeAnalysis.CSharp.Syntax;
 11using Microsoft.CodeAnalysis.Diagnostics;
 12
 13namespace NexusLabs.Needlr.Generators;
 14
 15/// <summary>
 16/// Analyzer that warns when [Options] classes use DataAnnotation validation attributes
 17/// that cannot be source-generated.
 18///
 19/// NDLRGEN030: DataAnnotation attribute cannot be source-generated
 20/// </summary>
 21[DiagnosticAnalyzer(LanguageNames.CSharp)]
 22public sealed class UnsupportedDataAnnotationAnalyzer : DiagnosticAnalyzer
 23{
 24    private const string OptionsAttributeName = "OptionsAttribute";
 25    private const string GeneratorsNamespace = "NexusLabs.Needlr.Generators";
 26    private const string DataAnnotationsNamespace = "System.ComponentModel.DataAnnotations";
 27
 128    private static readonly HashSet<string> SupportedDataAnnotations = new()
 129    {
 130        "RequiredAttribute",
 131        "RangeAttribute",
 132        "StringLengthAttribute",
 133        "MinLengthAttribute",
 134        "MaxLengthAttribute",
 135        "RegularExpressionAttribute",
 136        "EmailAddressAttribute",
 137        "PhoneAttribute",
 138        "UrlAttribute"
 139    };
 40
 41    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
 942        ImmutableArray.Create(DiagnosticDescriptors.UnsupportedDataAnnotation);
 43
 44    public override void Initialize(AnalysisContext context)
 45    {
 946        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
 947        context.EnableConcurrentExecution();
 48
 949        context.RegisterSyntaxNodeAction(AnalyzeClassDeclaration, SyntaxKind.ClassDeclaration);
 950        context.RegisterSyntaxNodeAction(AnalyzeRecordDeclaration, SyntaxKind.RecordDeclaration);
 951    }
 52
 53    private static void AnalyzeClassDeclaration(SyntaxNodeAnalysisContext context)
 54    {
 955        var classDeclaration = (ClassDeclarationSyntax)context.Node;
 956        AnalyzeTypeDeclaration(context, classDeclaration, classDeclaration.AttributeLists, classDeclaration.Members);
 957    }
 58
 59    private static void AnalyzeRecordDeclaration(SyntaxNodeAnalysisContext context)
 60    {
 161        var recordDeclaration = (RecordDeclarationSyntax)context.Node;
 162        AnalyzeTypeDeclaration(context, recordDeclaration, recordDeclaration.AttributeLists, recordDeclaration.Members);
 163    }
 64
 65    private static void AnalyzeTypeDeclaration(
 66        SyntaxNodeAnalysisContext context,
 67        TypeDeclarationSyntax typeDeclaration,
 68        SyntaxList<AttributeListSyntax> attributeLists,
 69        SyntaxList<MemberDeclarationSyntax> members)
 70    {
 71        // Check if this type has [Options] attribute
 1072        if (!HasOptionsAttribute(context, attributeLists))
 273            return;
 74
 875        var typeSymbol = context.SemanticModel.GetDeclaredSymbol(typeDeclaration);
 876        if (typeSymbol == null)
 077            return;
 78
 79        // Check all properties for unsupported DataAnnotations
 5280        foreach (var member in members)
 81        {
 1882            if (member is PropertyDeclarationSyntax property)
 83            {
 1884                AnalyzeProperty(context, typeSymbol, property);
 85            }
 86        }
 887    }
 88
 89    private static bool HasOptionsAttribute(SyntaxNodeAnalysisContext context, SyntaxList<AttributeListSyntax> attribute
 90    {
 2891        foreach (var attributeList in attributeLists)
 92        {
 2493            foreach (var attribute in attributeList.Attributes)
 94            {
 895                var symbol = context.SemanticModel.GetSymbolInfo(attribute).Symbol?.ContainingType;
 896                if (symbol != null &&
 897                    symbol.Name == OptionsAttributeName &&
 898                    symbol.ContainingNamespace.ToDisplayString() == GeneratorsNamespace)
 99                {
 8100                    return true;
 101                }
 102            }
 103        }
 2104        return false;
 105    }
 106
 107    private static void AnalyzeProperty(
 108        SyntaxNodeAnalysisContext context,
 109        INamedTypeSymbol containingType,
 110        PropertyDeclarationSyntax property)
 111    {
 18112        var propertySymbol = context.SemanticModel.GetDeclaredSymbol(property);
 18113        if (propertySymbol == null)
 0114            return;
 115
 74116        foreach (var attribute in propertySymbol.GetAttributes())
 117        {
 19118            var attrClass = attribute.AttributeClass;
 19119            if (attrClass == null)
 120                continue;
 121
 19122            var attrNamespace = attrClass.ContainingNamespace?.ToDisplayString() ?? "";
 19123            var attrTypeName = attrClass.Name;
 124
 125            // Only check DataAnnotations namespace
 19126            if (attrNamespace != DataAnnotationsNamespace)
 127                continue;
 128
 129            // Check if this is a ValidationAttribute (or subclass)
 18130            if (!IsValidationAttribute(attrClass))
 131                continue;
 132
 133            // Check if this is a supported attribute
 18134            if (SupportedDataAnnotations.Contains(attrTypeName))
 135                continue;
 136
 137            // Unsupported DataAnnotation - report diagnostic
 7138            var location = attribute.ApplicationSyntaxReference?.GetSyntax().GetLocation() ?? property.GetLocation();
 7139            var diagnostic = Diagnostic.Create(
 7140                DiagnosticDescriptors.UnsupportedDataAnnotation,
 7141                location,
 7142                attrTypeName,
 7143                containingType.Name,
 7144                propertySymbol.Name);
 145
 7146            context.ReportDiagnostic(diagnostic);
 147        }
 18148    }
 149
 150    private static bool IsValidationAttribute(INamedTypeSymbol attrClass)
 151    {
 18152        var current = attrClass;
 45153        while (current != null)
 154        {
 45155            var fullName = current.ToDisplayString();
 45156            if (fullName == "System.ComponentModel.DataAnnotations.ValidationAttribute")
 18157                return true;
 27158            current = current.BaseType;
 159        }
 0160        return false;
 161    }
 162}