< Summary

Information
Class: NexusLabs.Needlr.SignalR.Analyzers.HubPathAttributeAnalyzer
Assembly: NexusLabs.Needlr.SignalR.Analyzers
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.SignalR.Analyzers/HubPathAttributeAnalyzer.cs
Line coverage
81%
Covered lines: 50
Uncovered lines: 11
Coverable lines: 61
Total lines: 151
Line coverage: 81.9%
Branch coverage
74%
Covered branches: 43
Total branches: 58
Branch coverage: 74.1%
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.71%292890%
GetAttributeName(...)75%4483.33%
GetParameterName(...)83.33%66100%
IsFirstPositionalArgument(...)50%9875%
IsSecondPositionalArgument(...)50%9875%
AnalyzeHubPathArgument(...)100%22100%
AnalyzeHubTypeArgument(...)50%3233.33%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.SignalR.Analyzers/HubPathAttributeAnalyzer.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.SignalR.Analyzers;
 9
 10/// <summary>
 11/// Analyzer that validates HubPathAttribute usage for AOT compatibility.
 12/// </summary>
 13[DiagnosticAnalyzer(LanguageNames.CSharp)]
 14public sealed class HubPathAttributeAnalyzer : DiagnosticAnalyzer
 15{
 16    private const string HubPathAttributeName = "HubPathAttribute";
 17    private const string HubPathAttributeShortName = "HubPath";
 18
 19    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
 20520        ImmutableArray.Create(
 20521            DiagnosticDescriptors.HubPathMustBeConstant,
 20522            DiagnosticDescriptors.HubTypeMustBeTypeOf);
 23
 24    public override void Initialize(AnalysisContext context)
 25    {
 1826        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
 1827        context.EnableConcurrentExecution();
 28
 1829        context.RegisterSyntaxNodeAction(AnalyzeAttribute, SyntaxKind.Attribute);
 1830    }
 31
 32    private static void AnalyzeAttribute(SyntaxNodeAnalysisContext context)
 33    {
 2034        var attributeSyntax = (AttributeSyntax)context.Node;
 35
 36        // Check if this is a HubPathAttribute
 2037        var attributeName = GetAttributeName(attributeSyntax);
 2038        if (attributeName != HubPathAttributeName && attributeName != HubPathAttributeShortName)
 39        {
 1040            return;
 41        }
 42
 43        // Verify it's actually the Needlr HubPathAttribute via semantic model
 1044        var symbolInfo = context.SemanticModel.GetSymbolInfo(attributeSyntax, context.CancellationToken);
 1045        if (symbolInfo.Symbol is not IMethodSymbol attributeConstructor)
 46        {
 047            return;
 48        }
 49
 1050        var attributeType = attributeConstructor.ContainingType;
 1051        if (attributeType?.ContainingNamespace?.ToDisplayString() != "NexusLabs.Needlr.SignalR")
 52        {
 153            return;
 54        }
 55
 56        // Analyze the attribute arguments
 957        var argumentList = attributeSyntax.ArgumentList;
 958        if (argumentList == null)
 59        {
 060            return;
 61        }
 62
 5263        foreach (var argument in argumentList.Arguments)
 64        {
 1765            var parameterName = GetParameterName(argument, context.SemanticModel);
 66
 1767            if (parameterName == "hubPath" || (parameterName == null && IsFirstPositionalArgument(argument, argumentList
 68            {
 969                AnalyzeHubPathArgument(context, argument);
 70            }
 871            else if (parameterName == "hubType" || (parameterName == null && IsSecondPositionalArgument(argument, argume
 72            {
 873                AnalyzeHubTypeArgument(context, argument);
 74            }
 75        }
 976    }
 77
 78    private static string? GetAttributeName(AttributeSyntax attribute)
 79    {
 2080        return attribute.Name switch
 2081        {
 982            IdentifierNameSyntax identifier => identifier.Identifier.Text,
 1183            QualifiedNameSyntax qualified => qualified.Right.Identifier.Text,
 084            _ => null
 2085        };
 86    }
 87
 88    private static string? GetParameterName(AttributeArgumentSyntax argument, SemanticModel semanticModel)
 89    {
 1790        return argument.NameEquals?.Name.Identifier.Text ?? argument.NameColon?.Name.Identifier.Text;
 91    }
 92
 93    private static bool IsFirstPositionalArgument(AttributeArgumentSyntax argument, AttributeArgumentListSyntax argument
 94    {
 1195        if (argument.NameEquals != null || argument.NameColon != null)
 96        {
 097            return false;
 98        }
 99
 32100        var positionalArgs = argumentList.Arguments.Where(a => a.NameEquals == null && a.NameColon == null).ToList();
 11101        return positionalArgs.Count > 0 && positionalArgs[0] == argument;
 102    }
 103
 104    private static bool IsSecondPositionalArgument(AttributeArgumentSyntax argument, AttributeArgumentListSyntax argumen
 105    {
 5106        if (argument.NameEquals != null || argument.NameColon != null)
 107        {
 0108            return false;
 109        }
 110
 15111        var positionalArgs = argumentList.Arguments.Where(a => a.NameEquals == null && a.NameColon == null).ToList();
 5112        return positionalArgs.Count > 1 && positionalArgs[1] == argument;
 113    }
 114
 115    private static void AnalyzeHubPathArgument(SyntaxNodeAnalysisContext context, AttributeArgumentSyntax argument)
 116    {
 9117        var expression = argument.Expression;
 118
 119        // Check if it's a constant expression
 9120        var constantValue = context.SemanticModel.GetConstantValue(expression, context.CancellationToken);
 121
 9122        if (!constantValue.HasValue)
 123        {
 124            // Not a constant - report diagnostic
 4125            var expressionText = expression.ToString();
 4126            var diagnostic = Diagnostic.Create(
 4127                DiagnosticDescriptors.HubPathMustBeConstant,
 4128                expression.GetLocation(),
 4129                expressionText);
 130
 4131            context.ReportDiagnostic(diagnostic);
 132        }
 9133    }
 134
 135    private static void AnalyzeHubTypeArgument(SyntaxNodeAnalysisContext context, AttributeArgumentSyntax argument)
 136    {
 8137        var expression = argument.Expression;
 138
 139        // HubType must be a typeof expression
 8140        if (expression is not TypeOfExpressionSyntax)
 141        {
 0142            var expressionText = expression.ToString();
 0143            var diagnostic = Diagnostic.Create(
 0144                DiagnosticDescriptors.HubTypeMustBeTypeOf,
 0145                expression.GetLocation(),
 0146                expressionText);
 147
 0148            context.ReportDiagnostic(diagnostic);
 149        }
 8150    }
 151}