| | | 1 | | using System.Collections.Immutable; |
| | | 2 | | |
| | | 3 | | using Microsoft.CodeAnalysis; |
| | | 4 | | using Microsoft.CodeAnalysis.CSharp; |
| | | 5 | | using Microsoft.CodeAnalysis.CSharp.Syntax; |
| | | 6 | | using Microsoft.CodeAnalysis.Diagnostics; |
| | | 7 | | |
| | | 8 | | namespace NexusLabs.Needlr.Analyzers; |
| | | 9 | | |
| | | 10 | | /// <summary> |
| | | 11 | | /// Analyzer that validates [RegisterAs<T>] attribute usage: |
| | | 12 | | /// - NDLRCOR015: Type argument T is not an interface implemented by the class |
| | | 13 | | /// </summary> |
| | | 14 | | [DiagnosticAnalyzer(LanguageNames.CSharp)] |
| | | 15 | | public sealed class RegisterAsAttributeAnalyzer : DiagnosticAnalyzer |
| | | 16 | | { |
| | | 17 | | private const string RegisterAsAttributeName = "RegisterAsAttribute"; |
| | | 18 | | private const string NeedlrNamespace = "NexusLabs.Needlr"; |
| | | 19 | | |
| | | 20 | | public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => |
| | 162 | 21 | | ImmutableArray.Create(DiagnosticDescriptors.RegisterAsTypeArgNotImplemented); |
| | | 22 | | |
| | | 23 | | public override void Initialize(AnalysisContext context) |
| | | 24 | | { |
| | 15 | 25 | | context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); |
| | 15 | 26 | | context.EnableConcurrentExecution(); |
| | | 27 | | |
| | 15 | 28 | | context.RegisterSyntaxNodeAction(AnalyzeAttribute, SyntaxKind.Attribute); |
| | 15 | 29 | | } |
| | | 30 | | |
| | | 31 | | private static void AnalyzeAttribute(SyntaxNodeAnalysisContext context) |
| | | 32 | | { |
| | 84 | 33 | | var attributeSyntax = (AttributeSyntax)context.Node; |
| | 84 | 34 | | var attributeSymbol = context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol?.ContainingType; |
| | | 35 | | |
| | 84 | 36 | | if (attributeSymbol == null) |
| | 0 | 37 | | return; |
| | | 38 | | |
| | | 39 | | // Check if this is a [RegisterAs<T>] attribute |
| | 84 | 40 | | if (!IsRegisterAsAttribute(attributeSymbol)) |
| | 72 | 41 | | return; |
| | | 42 | | |
| | | 43 | | // Get the class this attribute is applied to |
| | 12 | 44 | | var classDeclaration = attributeSyntax.Parent?.Parent as ClassDeclarationSyntax; |
| | 12 | 45 | | if (classDeclaration == null) |
| | 0 | 46 | | return; |
| | | 47 | | |
| | 12 | 48 | | var classSymbol = context.SemanticModel.GetDeclaredSymbol(classDeclaration); |
| | 12 | 49 | | if (classSymbol == null) |
| | 0 | 50 | | return; |
| | | 51 | | |
| | | 52 | | // NDLRCOR015: Check if generic type argument is implemented by the class |
| | 12 | 53 | | if (attributeSymbol.IsGenericType && attributeSymbol.TypeArguments.Length == 1) |
| | | 54 | | { |
| | 12 | 55 | | var typeArg = attributeSymbol.TypeArguments[0] as INamedTypeSymbol; |
| | 12 | 56 | | if (typeArg != null) |
| | | 57 | | { |
| | | 58 | | // Check if the class implements this interface |
| | 12 | 59 | | bool implementsInterface = classSymbol.AllInterfaces.Any(i => |
| | 28 | 60 | | SymbolEqualityComparer.Default.Equals(i, typeArg)); |
| | | 61 | | |
| | 12 | 62 | | if (!implementsInterface) |
| | | 63 | | { |
| | 6 | 64 | | var diagnostic = Diagnostic.Create( |
| | 6 | 65 | | DiagnosticDescriptors.RegisterAsTypeArgNotImplemented, |
| | 6 | 66 | | attributeSyntax.GetLocation(), |
| | 6 | 67 | | classSymbol.Name, |
| | 6 | 68 | | typeArg.Name); |
| | | 69 | | |
| | 6 | 70 | | context.ReportDiagnostic(diagnostic); |
| | | 71 | | } |
| | | 72 | | } |
| | | 73 | | } |
| | 12 | 74 | | } |
| | | 75 | | |
| | | 76 | | private static bool IsRegisterAsAttribute(INamedTypeSymbol attributeSymbol) |
| | | 77 | | { |
| | | 78 | | // Handle RegisterAsAttribute<T> |
| | 84 | 79 | | var name = attributeSymbol.Name; |
| | 84 | 80 | | if (name != RegisterAsAttributeName) |
| | | 81 | | { |
| | | 82 | | // Check original definition for generic case |
| | 72 | 83 | | if (attributeSymbol.IsGenericType) |
| | | 84 | | { |
| | 0 | 85 | | name = attributeSymbol.OriginalDefinition.Name; |
| | 0 | 86 | | if (name != RegisterAsAttributeName) |
| | 0 | 87 | | return false; |
| | | 88 | | } |
| | | 89 | | else |
| | | 90 | | { |
| | 72 | 91 | | return false; |
| | | 92 | | } |
| | | 93 | | } |
| | | 94 | | |
| | 12 | 95 | | var ns = attributeSymbol.ContainingNamespace?.ToString(); |
| | 12 | 96 | | return ns == NeedlrNamespace; |
| | | 97 | | } |
| | | 98 | | } |