This repository has been archived by the owner on Dec 14, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create an analyzer to warn users not to decorate filters on page hand…
…ler methods Fixes #7684
- Loading branch information
Showing
19 changed files
with
576 additions
and
2 deletions.
There are no files selected for viewing
161 changes: 161 additions & 0 deletions
161
src/Microsoft.AspNetCore.Mvc.Analyzers/AttributesShouldNotBeAppliedToPageModelAnalyzer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
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) | ||
{ | ||
#if !DEBUG | ||
context.EnableConcurrentExecution(); | ||
#endif | ||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); | ||
|
||
TypeCache typeCache = null; | ||
context.RegisterCompilationStartAction(compilationStartAnalysisContext => | ||
{ | ||
typeCache = typeCache ?? new TypeCache(compilationStartAnalysisContext.Compilation); | ||
|
||
if (typeCache.PageModelAttribute == null || typeCache.PageModelAttribute.TypeKind == TypeKind.Error) | ||
{ | ||
// No-op if we can't find types we care about. | ||
return; | ||
} | ||
|
||
InitializeWorker(compilationStartAnalysisContext, typeCache); | ||
}); | ||
} | ||
|
||
private void InitializeWorker(CompilationStartAnalysisContext compilationStartAnalysisContext, TypeCache typeCache) | ||
{ | ||
compilationStartAnalysisContext.RegisterSymbolAction(symbolAnalysisContext => | ||
{ | ||
var method = (IMethodSymbol)symbolAnalysisContext.Symbol; | ||
|
||
var declaringType = method.ContainingType; | ||
if (!IsPageModel(declaringType, typeCache.PageModelAttribute)) | ||
{ | ||
return; | ||
} | ||
|
||
ReportFilterDiagnostic(ref symbolAnalysisContext, method, typeCache.IFilterMetadata); | ||
ReportFilterDiagnostic(ref symbolAnalysisContext, method, typeCache.AuthorizeAttribute); | ||
ReportFilterDiagnostic(ref symbolAnalysisContext, method, typeCache.AllowAnonymousAttribute); | ||
|
||
ReportRouteDiagnostic(ref symbolAnalysisContext, method, typeCache.IRouteTemplateProvider); | ||
}, SymbolKind.Method); | ||
|
||
compilationStartAnalysisContext.RegisterSymbolAction(symbolAnalysisContext => | ||
{ | ||
var type = (INamedTypeSymbol)symbolAnalysisContext.Symbol; | ||
if (!IsPageModel(type, typeCache.PageModelAttribute)) | ||
{ | ||
return; | ||
} | ||
|
||
ReportRouteDiagnosticOnModel(ref symbolAnalysisContext, type, typeCache.IRouteTemplateProvider); | ||
}, SymbolKind.NamedType); | ||
} | ||
|
||
private static bool IsPageModel(INamedTypeSymbol type, INamedTypeSymbol pageAttributeModel) | ||
{ | ||
return type.TypeKind == TypeKind.Class && | ||
!type.IsStatic && | ||
type.HasAttribute(pageAttributeModel, inherit: true); | ||
} | ||
|
||
private static void ReportRouteDiagnosticOnModel(ref SymbolAnalysisContext symbolAnalysisContext, INamedTypeSymbol typeSymbol, INamedTypeSymbol routeAttribute) | ||
{ | ||
var attribute = GetAttribute(typeSymbol, routeAttribute); | ||
if (attribute != null) | ||
{ | ||
var location = GetAttributeLocation(ref symbolAnalysisContext, attribute); | ||
|
||
symbolAnalysisContext.ReportDiagnostic(Diagnostic.Create( | ||
DiagnosticDescriptors.MVC1003_RouteAttributesShouldNotBeAppliedToPageModels, | ||
location, | ||
attribute.AttributeClass.Name)); | ||
} | ||
} | ||
|
||
private static void ReportRouteDiagnostic(ref SymbolAnalysisContext symbolAnalysisContext, IMethodSymbol method, INamedTypeSymbol routeAttribute) | ||
{ | ||
var attribute = GetAttribute(method, routeAttribute); | ||
if (attribute != null) | ||
{ | ||
var location = GetAttributeLocation(ref symbolAnalysisContext, attribute); | ||
|
||
symbolAnalysisContext.ReportDiagnostic(Diagnostic.Create( | ||
DiagnosticDescriptors.MVC1002_RouteAttributesShouldNotBeAppliedToPageHandlerMethods, | ||
location, | ||
attribute.AttributeClass.Name)); | ||
} | ||
} | ||
|
||
private static void ReportFilterDiagnostic(ref SymbolAnalysisContext symbolAnalysisContext, IMethodSymbol method, INamedTypeSymbol filterAttribute) | ||
{ | ||
var attribute = GetAttribute(method, filterAttribute); | ||
if (attribute != null) | ||
{ | ||
var location = GetAttributeLocation(ref symbolAnalysisContext, attribute); | ||
|
||
symbolAnalysisContext.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(ref SymbolAnalysisContext symbolAnalysisContext, AttributeData attribute) | ||
{ | ||
var syntax = attribute.ApplicationSyntaxReference.GetSyntax(symbolAnalysisContext.CancellationToken); | ||
return syntax?.GetLocation() ?? Location.None; | ||
} | ||
|
||
private 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; } | ||
} | ||
} | ||
} |
80 changes: 80 additions & 0 deletions
80
src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using Microsoft.CodeAnalysis; | ||
|
||
namespace Microsoft.AspNetCore.Mvc.Analyzers | ||
{ | ||
internal static class CodeAnalysisExtensions | ||
{ | ||
public static bool HasAttribute(this ITypeSymbol typeSymbol, ITypeSymbol attribute, bool inherit) | ||
{ | ||
foreach (var type in typeSymbol.GetTypeHierarchy()) | ||
{ | ||
if (type.HasAttribute(attribute)) | ||
{ | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
private static IEnumerable<ITypeSymbol> GetTypeHierarchy(this ITypeSymbol typeSymbol) | ||
{ | ||
while (typeSymbol != null) | ||
{ | ||
yield return typeSymbol; | ||
|
||
typeSymbol = typeSymbol.BaseType; | ||
} | ||
} | ||
|
||
public static bool HasAttribute(this ISymbol symbol, ITypeSymbol attribute) | ||
{ | ||
Debug.Assert(symbol != null); | ||
Debug.Assert(attribute != null); | ||
|
||
foreach (var declaredAttribute in symbol.GetAttributes()) | ||
{ | ||
if (declaredAttribute.AttributeClass == attribute) | ||
{ | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
public static bool IsAssignableFrom(this ITypeSymbol source, INamedTypeSymbol target) | ||
{ | ||
Debug.Assert(source != null); | ||
Debug.Assert(target != null); | ||
|
||
if (source.TypeKind == TypeKind.Interface) | ||
{ | ||
foreach (var @interface in target.AllInterfaces) | ||
{ | ||
if (source == @interface) | ||
{ | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
foreach (var type in target.GetTypeHierarchy()) | ||
{ | ||
if (source == type) | ||
{ | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.