|
// 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;
}
}
}
}
|