| File: TagHelpersInCodeBlocksAnalyzer.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.Generic; using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; namespace Microsoft.AspNetCore.Mvc.Analyzers; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class TagHelpersInCodeBlocksAnalyzer : DiagnosticAnalyzer { public TagHelpersInCodeBlocksAnalyzer() { SupportedDiagnostics = ImmutableArray.Create(DiagnosticDescriptors.MVC1006_FunctionsContainingTagHelpersMustBeAsyncAndReturnTask); } public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); // Generated Razor code is considered auto generated. By default analyzers skip over auto-generated code unless we say otherwise. context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); context.RegisterCompilationStartAction(context => { if (!SymbolCache.TryCreate(context.Compilation, out var symbolCache)) { // No-op if we can't find types we care about. return; } InitializeWorker(context, symbolCache); }); } private static void InitializeWorker(CompilationStartAnalysisContext context, SymbolCache symbolCache) { context.RegisterOperationBlockStartAction(startBlockContext => { var capturedDiagnosticLocations = new HashSet<Location>(); startBlockContext.RegisterOperationAction(context => { var awaitOperation = (IAwaitOperation)context.Operation; if (awaitOperation.Operation.Kind != OperationKind.Invocation) { return; } var invocationOperation = (IInvocationOperation)awaitOperation.Operation; if (!IsTagHelperRunnerRunAsync(invocationOperation.TargetMethod, symbolCache)) { return; } var parent = context.Operation.Parent; while (parent != null && !IsParentMethod(parent)) { parent = parent.Parent; } if (parent == null) { return; } var methodSymbol = (IMethodSymbol?)(parent switch { ILocalFunctionOperation localFunctionOperation => localFunctionOperation.Symbol, IAnonymousFunctionOperation anonymousFunctionOperation => anonymousFunctionOperation.Symbol, IMethodBodyOperation methodBodyOperation => startBlockContext.OwningSymbol, _ => null, }); if (methodSymbol == null) { // Unsupported operation type. return; } if (!methodSymbol.IsAsync || !symbolCache.TaskType.IsAssignableFrom(methodSymbol.ReturnType)) { capturedDiagnosticLocations.Add(parent.Syntax.GetLocation()); } }, OperationKind.Await); startBlockContext.RegisterOperationBlockEndAction(context => { foreach (var location in capturedDiagnosticLocations) { context.ReportDiagnostic( Diagnostic.Create(DiagnosticDescriptors.MVC1006_FunctionsContainingTagHelpersMustBeAsyncAndReturnTask, location)); } }); }); } private static bool IsTagHelperRunnerRunAsync(IMethodSymbol method, SymbolCache symbolCache) { if (!SymbolEqualityComparer.Default.Equals(method, symbolCache.TagHelperRunnerRunAsyncMethodSymbol)) { return false; } return true; } private static bool IsParentMethod(IOperation operation) { if (operation.Kind == OperationKind.LocalFunction) { return true; } if (operation.Kind == OperationKind.MethodBody) { return true; } if (operation.Kind == OperationKind.AnonymousFunction) { return true; } return false; } private readonly struct SymbolCache { private SymbolCache( IMethodSymbol tagHelperRunnerRunAsyncMethodSymbol, INamedTypeSymbol taskType) { TagHelperRunnerRunAsyncMethodSymbol = tagHelperRunnerRunAsyncMethodSymbol; TaskType = taskType; } public IMethodSymbol TagHelperRunnerRunAsyncMethodSymbol { get; } public INamedTypeSymbol TaskType { get; } public static bool TryCreate(Compilation compilation, out SymbolCache symbolCache) { symbolCache = default; if (!TryGetType(SymbolNames.TagHelperRunnerTypeName, out var tagHelperRunnerType)) { return false; } if (!TryGetType(SymbolNames.TaskTypeName, out var taskType)) { return false; } var members = tagHelperRunnerType.GetMembers(SymbolNames.RunAsyncMethodName); if (members.Length == 0) { return false; } var tagHelperRunnerRunAsyncMethod = (IMethodSymbol)members[0]; symbolCache = new SymbolCache(tagHelperRunnerRunAsyncMethod, taskType); return true; bool TryGetType(string typeName, out INamedTypeSymbol typeSymbol) { typeSymbol = compilation.GetTypeByMetadataName(typeName); return typeSymbol != null && typeSymbol.TypeKind != TypeKind.Error; } } } }