| | | 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 warns when [DoNotAutoRegister] is applied directly to a class |
| | | 12 | | /// that implements a Needlr plugin interface. |
| | | 13 | | /// </summary> |
| | | 14 | | [DiagnosticAnalyzer(LanguageNames.CSharp)] |
| | | 15 | | public sealed class DoNotAutoRegisterOnPluginAnalyzer : DiagnosticAnalyzer |
| | | 16 | | { |
| | 1 | 17 | | private static readonly ImmutableHashSet<string> PluginInterfaceNames = ImmutableHashSet.Create( |
| | 1 | 18 | | "IServiceCollectionPlugin", |
| | 1 | 19 | | "IPostBuildServiceCollectionPlugin", |
| | 1 | 20 | | "IWebApplicationPlugin", |
| | 1 | 21 | | "IWebApplicationBuilderPlugin", |
| | 1 | 22 | | "IHostApplicationBuilderPlugin"); |
| | | 23 | | |
| | | 24 | | private const string DoNotAutoRegisterAttributeName = "DoNotAutoRegisterAttribute"; |
| | | 25 | | private const string DoNotAutoRegisterShortName = "DoNotAutoRegister"; |
| | | 26 | | |
| | | 27 | | public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => |
| | 133 | 28 | | ImmutableArray.Create(DiagnosticDescriptors.DoNotAutoRegisterOnPluginClass); |
| | | 29 | | |
| | | 30 | | public override void Initialize(AnalysisContext context) |
| | | 31 | | { |
| | 13 | 32 | | context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); |
| | 13 | 33 | | context.EnableConcurrentExecution(); |
| | 13 | 34 | | context.RegisterSyntaxNodeAction(AnalyzeClassDeclaration, SyntaxKind.ClassDeclaration); |
| | 13 | 35 | | } |
| | | 36 | | |
| | | 37 | | private static void AnalyzeClassDeclaration(SyntaxNodeAnalysisContext context) |
| | | 38 | | { |
| | 24 | 39 | | var classDeclaration = (ClassDeclarationSyntax)context.Node; |
| | | 40 | | |
| | 24 | 41 | | if (!HasDoNotAutoRegisterDirectly(classDeclaration, context.SemanticModel, out var attributeLocation)) |
| | 17 | 42 | | return; |
| | | 43 | | |
| | 7 | 44 | | if (!ImplementsPluginInterface(classDeclaration, context.SemanticModel)) |
| | 1 | 45 | | return; |
| | | 46 | | |
| | 6 | 47 | | var diagnostic = Diagnostic.Create( |
| | 6 | 48 | | DiagnosticDescriptors.DoNotAutoRegisterOnPluginClass, |
| | 6 | 49 | | attributeLocation, |
| | 6 | 50 | | classDeclaration.Identifier.Text); |
| | | 51 | | |
| | 6 | 52 | | context.ReportDiagnostic(diagnostic); |
| | 6 | 53 | | } |
| | | 54 | | |
| | | 55 | | private static bool HasDoNotAutoRegisterDirectly( |
| | | 56 | | ClassDeclarationSyntax classDeclaration, |
| | | 57 | | SemanticModel semanticModel, |
| | | 58 | | out Location? attributeLocation) |
| | | 59 | | { |
| | 24 | 60 | | attributeLocation = null; |
| | 87 | 61 | | foreach (var attributeList in classDeclaration.AttributeLists) |
| | | 62 | | { |
| | 85 | 63 | | foreach (var attribute in attributeList.Attributes) |
| | | 64 | | { |
| | 23 | 65 | | var name = attribute.Name.ToString(); |
| | 23 | 66 | | if (name == DoNotAutoRegisterShortName || name == DoNotAutoRegisterAttributeName || |
| | 23 | 67 | | name.EndsWith("." + DoNotAutoRegisterShortName) || name.EndsWith("." + DoNotAutoRegisterAttributeNam |
| | | 68 | | { |
| | 7 | 69 | | var symbolInfo = semanticModel.GetSymbolInfo(attribute); |
| | 7 | 70 | | var attributeClass = symbolInfo.Symbol?.ContainingType |
| | 7 | 71 | | ?? symbolInfo.CandidateSymbols.FirstOrDefault()?.ContainingType; |
| | | 72 | | |
| | 7 | 73 | | if (attributeClass != null) |
| | | 74 | | { |
| | 7 | 75 | | var fullName = attributeClass.ToDisplayString(); |
| | 7 | 76 | | if (fullName != "NexusLabs.Needlr.DoNotAutoRegisterAttribute") |
| | | 77 | | continue; |
| | | 78 | | } |
| | | 79 | | |
| | 7 | 80 | | attributeLocation = attributeList.GetLocation(); |
| | 7 | 81 | | return true; |
| | | 82 | | } |
| | | 83 | | } |
| | | 84 | | } |
| | 17 | 85 | | return false; |
| | | 86 | | } |
| | | 87 | | |
| | | 88 | | private static bool ImplementsPluginInterface( |
| | | 89 | | ClassDeclarationSyntax classDeclaration, |
| | | 90 | | SemanticModel semanticModel) |
| | | 91 | | { |
| | 7 | 92 | | if (classDeclaration.BaseList == null) |
| | 1 | 93 | | return false; |
| | | 94 | | |
| | 6 | 95 | | if (semanticModel.GetDeclaredSymbol(classDeclaration) is not INamedTypeSymbol classSymbol) |
| | 0 | 96 | | return false; |
| | | 97 | | |
| | 18 | 98 | | foreach (var iface in classSymbol.AllInterfaces) |
| | | 99 | | { |
| | 6 | 100 | | if (PluginInterfaceNames.Contains(iface.Name) && |
| | 6 | 101 | | iface.ContainingNamespace?.ToString() == "NexusLabs.Needlr") |
| | | 102 | | { |
| | 6 | 103 | | return true; |
| | | 104 | | } |
| | | 105 | | } |
| | | 106 | | |
| | 0 | 107 | | return false; |
| | | 108 | | } |
| | | 109 | | } |