103 lines
4.6 KiB
C#
103 lines
4.6 KiB
C#
// 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;
|
|
using System.Collections.Immutable;
|
|
using System.Linq;
|
|
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp;
|
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
using Microsoft.CodeAnalysis.Diagnostics;
|
|
|
|
namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|
{
|
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
|
public class ApiActionsShouldUseActionResultOfTAnalyzer : ApiControllerAnalyzerBase
|
|
{
|
|
public static readonly string ReturnTypeKey = "ReturnType";
|
|
|
|
public ApiActionsShouldUseActionResultOfTAnalyzer()
|
|
: base(DiagnosticDescriptors.MVC1002_ApiActionsShouldReturnActionResultOf)
|
|
{
|
|
}
|
|
|
|
protected override void InitializeWorker(ApiControllerAnalyzerContext analyzerContext)
|
|
{
|
|
analyzerContext.Context.RegisterSyntaxNodeAction(context =>
|
|
{
|
|
var methodSyntax = (MethodDeclarationSyntax)context.Node;
|
|
if (methodSyntax.Body == null)
|
|
{
|
|
// Ignore expression bodied methods.
|
|
}
|
|
|
|
var method = context.SemanticModel.GetDeclaredSymbol(methodSyntax, context.CancellationToken);
|
|
if (!analyzerContext.IsApiAction(method))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (method.ReturnsVoid || method.ReturnType.Kind != SymbolKind.NamedType)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var declaredReturnType = method.ReturnType;
|
|
var namedReturnType = (INamedTypeSymbol)method.ReturnType;
|
|
var isTaskOActionResult = false;
|
|
if (namedReturnType.ConstructedFrom?.IsAssignableFrom(analyzerContext.SystemThreadingTaskOfT) ?? false)
|
|
{
|
|
// Unwrap Task<T>.
|
|
isTaskOActionResult = true;
|
|
declaredReturnType = namedReturnType.TypeArguments[0];
|
|
}
|
|
|
|
if (!declaredReturnType.IsAssignableFrom(analyzerContext.IActionResult))
|
|
{
|
|
// Method signature does not look like IActionResult MyAction or SomeAwaitable<IActionResult>.
|
|
// Nothing to do here.
|
|
return;
|
|
}
|
|
|
|
// Method returns an IActionResult. Determine if the method block returns an ObjectResult
|
|
foreach (var returnStatement in methodSyntax.DescendantNodes().OfType<ReturnStatementSyntax>())
|
|
{
|
|
var returnType = context.SemanticModel.GetTypeInfo(returnStatement.Expression, context.CancellationToken);
|
|
if (returnType.Type == null || returnType.Type.Kind == SymbolKind.ErrorType)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ImmutableDictionary<string, string> properties = null;
|
|
if (returnType.Type.IsAssignableFrom(analyzerContext.ObjectResult))
|
|
{
|
|
// Check if the method signature looks like "return Ok(userModelInstance)". If so, we can infer the type of userModelInstance
|
|
if (returnStatement.Expression is InvocationExpressionSyntax invocation &&
|
|
invocation.ArgumentList.Arguments.Count == 1)
|
|
{
|
|
var typeInfo = context.SemanticModel.GetTypeInfo(invocation.ArgumentList.Arguments[0].Expression);
|
|
var desiredReturnType = analyzerContext.ActionResultOfT.Construct(typeInfo.Type);
|
|
if (isTaskOActionResult)
|
|
{
|
|
desiredReturnType = analyzerContext.SystemThreadingTaskOfT.Construct(desiredReturnType);
|
|
}
|
|
|
|
var desiredReturnTypeString = desiredReturnType.ToMinimalDisplayString(
|
|
context.SemanticModel,
|
|
methodSyntax.ReturnType.SpanStart);
|
|
|
|
properties = ImmutableDictionary.Create<string, string>(StringComparer.Ordinal)
|
|
.Add(ReturnTypeKey, desiredReturnTypeString);
|
|
}
|
|
|
|
context.ReportDiagnostic(Diagnostic.Create(
|
|
SupportedDiagnostic,
|
|
methodSyntax.ReturnType.GetLocation(),
|
|
properties: properties));
|
|
}
|
|
}
|
|
}, SyntaxKind.MethodDeclaration);
|
|
}
|
|
}
|
|
}
|