< Summary

Information
Class: NexusLabs.Needlr.Analyzers.ReflectionInAotProjectAnalyzer
Assembly: NexusLabs.Needlr.Analyzers
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Analyzers/ReflectionInAotProjectAnalyzer.cs
Line coverage
92%
Covered lines: 69
Uncovered lines: 6
Coverable lines: 75
Total lines: 140
Line coverage: 92%
Branch coverage
86%
Covered branches: 26
Total branches: 30
Branch coverage: 86.6%
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%22100%
IsAotEnabledProject(...)100%88100%
AnalyzeInvocation(...)80%111081.25%
AnalyzeObjectCreation(...)80%111081.25%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Analyzers/ReflectionInAotProjectAnalyzer.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 detects reflection-based Needlr API usage in AOT-enabled projects.
 12/// </summary>
 13[DiagnosticAnalyzer(LanguageNames.CSharp)]
 14public sealed class ReflectionInAotProjectAnalyzer : DiagnosticAnalyzer
 15{
 16    // Reflection-based method names that should trigger the diagnostic
 117    private static readonly ImmutableHashSet<string> ReflectionMethodNames = ImmutableHashSet.Create(
 118        "UsingReflectionTypeRegistrar",
 119        "UsingReflectionTypeFilterer",
 120        "UsingReflectionPluginFactory",
 121        "UsingReflectionAssemblyLoader",
 122        "UsingReflectionAssemblyProvider");
 23
 24    // Reflection-based type names that should trigger the diagnostic
 125    private static readonly ImmutableHashSet<string> ReflectionTypeNames = ImmutableHashSet.Create(
 126        "ReflectionPluginFactory",
 127        "ReflectionTypeRegistrar",
 128        "ReflectionTypeFilterer",
 129        "ReflectionAssemblyLoader",
 130        "ReflectionAssemblyProvider",
 131        "ReflectionServiceProviderBuilder");
 32
 33    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
 24234        ImmutableArray.Create(DiagnosticDescriptors.ReflectionInAotProject);
 35
 36    public override void Initialize(AnalysisContext context)
 37    {
 1438        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
 1439        context.EnableConcurrentExecution();
 40
 1441        context.RegisterCompilationStartAction(compilationContext =>
 1442        {
 1443            // Check if project has AOT/trimming enabled
 944            if (!IsAotEnabledProject(compilationContext.Options))
 1445            {
 146                return;
 1447            }
 1448
 1449            // Register syntax node analysis for method invocations
 850            compilationContext.RegisterSyntaxNodeAction(
 851                AnalyzeInvocation,
 852                SyntaxKind.InvocationExpression);
 1453
 1454            // Register syntax node analysis for object creation
 855            compilationContext.RegisterSyntaxNodeAction(
 856                AnalyzeObjectCreation,
 857                SyntaxKind.ObjectCreationExpression);
 2258        });
 1459    }
 60
 61    private static bool IsAotEnabledProject(AnalyzerOptions options)
 62    {
 63        // Check for PublishAot or IsAotCompatible in analyzer config
 964        var globalOptions = options.AnalyzerConfigOptionsProvider.GlobalOptions;
 65
 966        if (globalOptions.TryGetValue("build_property.PublishAot", out var publishAot) &&
 967            string.Equals(publishAot, "true", System.StringComparison.OrdinalIgnoreCase))
 68        {
 669            return true;
 70        }
 71
 372        if (globalOptions.TryGetValue("build_property.IsAotCompatible", out var isAotCompatible) &&
 373            string.Equals(isAotCompatible, "true", System.StringComparison.OrdinalIgnoreCase))
 74        {
 275            return true;
 76        }
 77
 178        return false;
 79    }
 80
 81    private static void AnalyzeInvocation(SyntaxNodeAnalysisContext context)
 82    {
 683        var invocation = (InvocationExpressionSyntax)context.Node;
 84
 85        // Get the method name being invoked
 686        string? methodName = null;
 687        Location? location = null;
 88
 689        if (invocation.Expression is MemberAccessExpressionSyntax memberAccess)
 90        {
 691            methodName = memberAccess.Name.Identifier.Text;
 692            location = memberAccess.Name.GetLocation();
 93        }
 094        else if (invocation.Expression is IdentifierNameSyntax identifier)
 95        {
 096            methodName = identifier.Identifier.Text;
 097            location = identifier.GetLocation();
 98        }
 99
 6100        if (methodName != null && location != null && ReflectionMethodNames.Contains(methodName))
 101        {
 6102            var diagnostic = Diagnostic.Create(
 6103                DiagnosticDescriptors.ReflectionInAotProject,
 6104                location,
 6105                methodName);
 106
 6107            context.ReportDiagnostic(diagnostic);
 108        }
 6109    }
 110
 111    private static void AnalyzeObjectCreation(SyntaxNodeAnalysisContext context)
 112    {
 8113        var objectCreation = (ObjectCreationExpressionSyntax)context.Node;
 114
 115        // Get the type name being instantiated
 8116        string? typeName = null;
 8117        Location? location = null;
 118
 8119        if (objectCreation.Type is IdentifierNameSyntax identifier)
 120        {
 8121            typeName = identifier.Identifier.Text;
 8122            location = identifier.GetLocation();
 123        }
 0124        else if (objectCreation.Type is QualifiedNameSyntax qualifiedName)
 125        {
 0126            typeName = qualifiedName.Right.Identifier.Text;
 0127            location = qualifiedName.Right.GetLocation();
 128        }
 129
 8130        if (typeName != null && location != null && ReflectionTypeNames.Contains(typeName))
 131        {
 4132            var diagnostic = Diagnostic.Create(
 4133                DiagnosticDescriptors.ReflectionInAotProject,
 4134                location,
 4135                typeName);
 136
 4137            context.ReportDiagnostic(diagnostic);
 138        }
 8139    }
 140}