< Summary

Information
Class: NexusLabs.Needlr.Analyzers.InterceptAttributeAnalyzer
Assembly: NexusLabs.Needlr.Analyzers
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Analyzers/InterceptAttributeAnalyzer.cs
Line coverage
96%
Covered lines: 53
Uncovered lines: 2
Coverable lines: 55
Total lines: 146
Line coverage: 96.3%
Branch coverage
70%
Covered branches: 34
Total branches: 48
Branch coverage: 70.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_SupportedDiagnostics()100%11100%
Initialize(...)100%11100%
AnalyzeAttribute(...)85%202096.29%
IsInterceptAttribute(...)75%44100%
GetInterceptorType(...)70%101087.5%
ImplementsIMethodInterceptor(...)50%44100%
IsSystemInterface(...)66.66%66100%
IsNeedlrInternalInterface(...)25%44100%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Analyzers/InterceptAttributeAnalyzer.cs

#LineLine coverage
 1using System.Collections.Immutable;
 2
 3using Microsoft.CodeAnalysis;
 4using Microsoft.CodeAnalysis.CSharp;
 5using Microsoft.CodeAnalysis.CSharp.Syntax;
 6using Microsoft.CodeAnalysis.Diagnostics;
 7
 8namespace NexusLabs.Needlr.Analyzers;
 9
 10/// <summary>
 11/// Analyzer that validates [Intercept] attribute usage:
 12/// - NDLRCOR007: Intercept type must implement IMethodInterceptor
 13/// - NDLRCOR008: [Intercept] applied to class without interfaces
 14/// </summary>
 15[DiagnosticAnalyzer(LanguageNames.CSharp)]
 16public sealed class InterceptAttributeAnalyzer : DiagnosticAnalyzer
 17{
 18    private const string InterceptAttributeName = "InterceptAttribute";
 19    private const string IMethodInterceptorName = "IMethodInterceptor";
 20    private const string NeedlrNamespace = "NexusLabs.Needlr";
 21
 22    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
 15223        ImmutableArray.Create(
 15224            DiagnosticDescriptors.InterceptTypeMustImplementInterface,
 15225            DiagnosticDescriptors.InterceptOnClassWithoutInterfaces);
 26
 27    public override void Initialize(AnalysisContext context)
 28    {
 1329        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
 1330        context.EnableConcurrentExecution();
 31
 1332        context.RegisterSyntaxNodeAction(AnalyzeAttribute, SyntaxKind.Attribute);
 1333    }
 34
 35    private static void AnalyzeAttribute(SyntaxNodeAnalysisContext context)
 36    {
 4037        var attributeSyntax = (AttributeSyntax)context.Node;
 4038        var attributeSymbol = context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol?.ContainingType;
 39
 4040        if (attributeSymbol == null)
 041            return;
 42
 43        // Check if this is an [Intercept] or [Intercept<T>] attribute
 4044        if (!IsInterceptAttribute(attributeSymbol))
 3245            return;
 46
 47        // Get the interceptor type from the attribute
 848        var interceptorType = GetInterceptorType(attributeSyntax, attributeSymbol, context.SemanticModel);
 49
 850        if (interceptorType != null)
 51        {
 52            // NDLRCOR007: Check if interceptor implements IMethodInterceptor
 853            if (!ImplementsIMethodInterceptor(interceptorType))
 54            {
 255                var diagnostic = Diagnostic.Create(
 256                    DiagnosticDescriptors.InterceptTypeMustImplementInterface,
 257                    attributeSyntax.GetLocation(),
 258                    interceptorType.Name);
 59
 260                context.ReportDiagnostic(diagnostic);
 61            }
 62        }
 63
 64        // Find the class/method this attribute is applied to
 865        var targetNode = attributeSyntax.Parent?.Parent;
 66
 67        // If applied to a class, check NDLRCOR008
 868        if (targetNode is ClassDeclarationSyntax classDeclaration)
 69        {
 770            var classSymbol = context.SemanticModel.GetDeclaredSymbol(classDeclaration);
 771            if (classSymbol != null)
 72            {
 73                // Check if class implements any non-system interfaces
 774                var hasUserInterface = classSymbol.AllInterfaces.Any(i =>
 1275                    !IsSystemInterface(i) && !IsNeedlrInternalInterface(i));
 76
 777                if (!hasUserInterface)
 78                {
 479                    var diagnostic = Diagnostic.Create(
 480                        DiagnosticDescriptors.InterceptOnClassWithoutInterfaces,
 481                        attributeSyntax.GetLocation(),
 482                        classDeclaration.Identifier.Text);
 83
 484                    context.ReportDiagnostic(diagnostic);
 85                }
 86            }
 87        }
 888    }
 89
 90    private static bool IsInterceptAttribute(INamedTypeSymbol attributeSymbol)
 91    {
 92        // Check for InterceptAttribute or InterceptAttribute<T>
 4093        var name = attributeSymbol.Name;
 4094        if (name != InterceptAttributeName)
 3295            return false;
 96
 897        var ns = attributeSymbol.ContainingNamespace?.ToString();
 898        return ns == NeedlrNamespace;
 99    }
 100
 101    private static INamedTypeSymbol? GetInterceptorType(
 102        AttributeSyntax attributeSyntax,
 103        INamedTypeSymbol attributeSymbol,
 104        SemanticModel semanticModel)
 105    {
 106        // Generic attribute: [Intercept<LoggingInterceptor>]
 8107        if (attributeSymbol.IsGenericType && attributeSymbol.TypeArguments.Length == 1)
 108        {
 6109            return attributeSymbol.TypeArguments[0] as INamedTypeSymbol;
 110        }
 111
 112        // Non-generic attribute: [Intercept(typeof(LoggingInterceptor))]
 2113        if (attributeSyntax.ArgumentList?.Arguments.Count > 0)
 114        {
 2115            var firstArg = attributeSyntax.ArgumentList.Arguments[0].Expression;
 2116            if (firstArg is TypeOfExpressionSyntax typeOfExpr)
 117            {
 2118                var typeInfo = semanticModel.GetTypeInfo(typeOfExpr.Type);
 2119                return typeInfo.Type as INamedTypeSymbol;
 120            }
 121        }
 122
 0123        return null;
 124    }
 125
 126    private static bool ImplementsIMethodInterceptor(INamedTypeSymbol typeSymbol)
 127    {
 8128        return typeSymbol.AllInterfaces.Any(i =>
 14129            i.Name == IMethodInterceptorName &&
 14130            i.ContainingNamespace?.ToString() == NeedlrNamespace);
 131    }
 132
 133    private static bool IsSystemInterface(INamedTypeSymbol interfaceSymbol)
 134    {
 5135        var ns = interfaceSymbol.ContainingNamespace?.ToString() ?? "";
 5136        return ns.StartsWith("System", StringComparison.Ordinal) ||
 5137               ns.StartsWith("Microsoft", StringComparison.Ordinal);
 138    }
 139
 140    private static bool IsNeedlrInternalInterface(INamedTypeSymbol interfaceSymbol)
 141    {
 142        // IMethodInterceptor is an internal Needlr interface, not a user service interface
 3143        return interfaceSymbol.Name == IMethodInterceptorName &&
 3144               interfaceSymbol.ContainingNamespace?.ToString() == NeedlrNamespace;
 145    }
 146}