| File: AttributesShouldNotBeAppliedToPageModelAnalyzer.cs | Web Access |
| Project: src\aspnetcore\src\Mvc\Mvc.Analyzers\src\Microsoft.AspNetCore.Mvc.Analyzers.csproj (Microsoft.AspNetCore.Mvc.Analyzers) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; namespace Microsoft.AspNetCore.Mvc.Analyzers; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class AttributesShouldNotBeAppliedToPageModelAnalyzer : DiagnosticAnalyzer { public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create( DiagnosticDescriptors.MVC1001_FiltersShouldNotBeAppliedToPageHandlerMethods, DiagnosticDescriptors.MVC1002_RouteAttributesShouldNotBeAppliedToPageHandlerMethods, DiagnosticDescriptors.MVC1003_RouteAttributesShouldNotBeAppliedToPageModels); public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.RegisterCompilationStartAction(context => { var typeCache = new TypeCache(context.Compilation); if (typeCache.PageModelAttribute == null || typeCache.PageModelAttribute.TypeKind == TypeKind.Error) { // No-op if we can't find types we care about. return; } InitializeWorker(context, typeCache); }); } private static void InitializeWorker(CompilationStartAnalysisContext context, TypeCache typeCache) { context.RegisterSymbolAction(context => { var method = (IMethodSymbol)context.Symbol; var declaringType = method.ContainingType; if (!IsPageModel(declaringType, typeCache.PageModelAttribute) || !IsPageHandlerMethod(method)) { return; } ReportFilterDiagnostic(context, method, typeCache.IFilterMetadata); ReportFilterDiagnostic(context, method, typeCache.AuthorizeAttribute); ReportFilterDiagnostic(context, method, typeCache.AllowAnonymousAttribute); ReportRouteDiagnostic(context, method, typeCache.IRouteTemplateProvider); }, SymbolKind.Method); context.RegisterSymbolAction(context => { var type = (INamedTypeSymbol)context.Symbol; if (!IsPageModel(type, typeCache.PageModelAttribute)) { return; } ReportRouteDiagnosticOnModel(context, type, typeCache.IRouteTemplateProvider); }, SymbolKind.NamedType); } private static bool IsPageHandlerMethod(IMethodSymbol method) { return method.MethodKind == MethodKind.Ordinary && !method.IsStatic && !method.IsGenericMethod && method.DeclaredAccessibility == Accessibility.Public; } private static bool IsPageModel(INamedTypeSymbol type, INamedTypeSymbol pageAttributeModel) { return type.TypeKind == TypeKind.Class && !type.IsStatic && type.HasAttribute(pageAttributeModel, inherit: true); } private static void ReportRouteDiagnosticOnModel(SymbolAnalysisContext context, INamedTypeSymbol typeSymbol, INamedTypeSymbol routeAttribute) { var attribute = GetAttribute(typeSymbol, routeAttribute); if (attribute != null) { var location = GetAttributeLocation(context, attribute); context.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.MVC1003_RouteAttributesShouldNotBeAppliedToPageModels, location, attribute.AttributeClass.Name)); } } private static void ReportRouteDiagnostic(SymbolAnalysisContext context, IMethodSymbol method, INamedTypeSymbol routeAttribute) { var attribute = GetAttribute(method, routeAttribute); if (attribute != null) { var location = GetAttributeLocation(context, attribute); context.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.MVC1002_RouteAttributesShouldNotBeAppliedToPageHandlerMethods, location, attribute.AttributeClass.Name)); } } private static void ReportFilterDiagnostic(SymbolAnalysisContext context, IMethodSymbol method, INamedTypeSymbol filterAttribute) { var attribute = GetAttribute(method, filterAttribute); if (attribute != null) { var location = GetAttributeLocation(context, attribute); context.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.MVC1001_FiltersShouldNotBeAppliedToPageHandlerMethods, location, attribute.AttributeClass.Name)); } } private static AttributeData? GetAttribute(ISymbol symbol, INamedTypeSymbol attributeType) { foreach (var attribute in symbol.GetAttributes()) { if (attributeType.IsAssignableFrom(attribute.AttributeClass)) { return attribute; } } return null; } private static Location GetAttributeLocation(SymbolAnalysisContext context, AttributeData attribute) { var syntax = attribute.ApplicationSyntaxReference.GetSyntax(context.CancellationToken); return syntax?.GetLocation() ?? Location.None; } private sealed class TypeCache { public TypeCache(Compilation compilation) { PageModelAttribute = compilation.GetTypeByMetadataName(SymbolNames.PageModelAttributeType); IFilterMetadata = compilation.GetTypeByMetadataName(SymbolNames.IFilterMetadataType); AuthorizeAttribute = compilation.GetTypeByMetadataName(SymbolNames.AuthorizeAttribute); AllowAnonymousAttribute = compilation.GetTypeByMetadataName(SymbolNames.AllowAnonymousAttribute); IRouteTemplateProvider = compilation.GetTypeByMetadataName(SymbolNames.IRouteTemplateProvider); } public INamedTypeSymbol PageModelAttribute { get; } public INamedTypeSymbol IFilterMetadata { get; } public INamedTypeSymbol AuthorizeAttribute { get; } public INamedTypeSymbol AllowAnonymousAttribute { get; } public INamedTypeSymbol IRouteTemplateProvider { get; } } }