Use StatusCodes constants instead of literals in the ProducesResponseType code fix (#8234)
* Use StatusCodes constants instead of literals
This commit is contained in:
parent
3c19cede7d
commit
da1189e6f1
|
|
@ -40,9 +40,12 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers
|
|||
}
|
||||
|
||||
var documentEditor = await DocumentEditor.CreateAsync(_document, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var addUsingDirective = false;
|
||||
foreach (var statusCode in statusCodes.OrderBy(s => s))
|
||||
{
|
||||
documentEditor.AddAttribute(context.MethodSyntax, CreateProducesResponseTypeAttribute(statusCode));
|
||||
documentEditor.AddAttribute(context.MethodSyntax, CreateProducesResponseTypeAttribute(context, statusCode, out var addUsing));
|
||||
addUsingDirective |= addUsing;
|
||||
}
|
||||
|
||||
if (!declaredResponseMetadata.Any(m => m.IsDefault && m.AttributeSource == context.Method))
|
||||
|
|
@ -64,7 +67,23 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers
|
|||
documentEditor.RemoveNode(attributeSyntax);
|
||||
}
|
||||
|
||||
return documentEditor.GetChangedDocument();
|
||||
var document = documentEditor.GetChangedDocument();
|
||||
|
||||
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (root is CompilationUnitSyntax compilationUnit && addUsingDirective)
|
||||
{
|
||||
const string @namespace = "Microsoft.AspNetCore.Http";
|
||||
|
||||
var declaredUsings = new HashSet<string>(compilationUnit.Usings.Select(x => x.Name.ToString()));
|
||||
|
||||
if (!declaredUsings.Contains(@namespace))
|
||||
{
|
||||
root = compilationUnit.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(@namespace)));
|
||||
}
|
||||
}
|
||||
|
||||
return document.WithSyntaxRoot(root);
|
||||
}
|
||||
|
||||
private async Task<CodeActionContext> CreateCodeActionContext(CancellationToken cancellationToken)
|
||||
|
|
@ -75,12 +94,37 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers
|
|||
var methodSyntax = methodReturnStatement.FirstAncestorOrSelf<MethodDeclarationSyntax>();
|
||||
var method = semanticModel.GetDeclaredSymbol(methodSyntax, cancellationToken);
|
||||
|
||||
var statusCodesType = semanticModel.Compilation.GetTypeByMetadataName(ApiSymbolNames.HttpStatusCodes);
|
||||
var statusCodeConstants = GetStatusCodeConstants(statusCodesType);
|
||||
|
||||
var symbolCache = new ApiControllerSymbolCache(semanticModel.Compilation);
|
||||
|
||||
var codeActionContext = new CodeActionContext(semanticModel, symbolCache, method, methodSyntax, cancellationToken);
|
||||
var codeActionContext = new CodeActionContext(semanticModel, symbolCache, method, methodSyntax, statusCodeConstants, cancellationToken);
|
||||
return codeActionContext;
|
||||
}
|
||||
|
||||
private static Dictionary<int, string> GetStatusCodeConstants(INamespaceOrTypeSymbol statusCodesType)
|
||||
{
|
||||
var statusCodeConstants = new Dictionary<int, string>();
|
||||
|
||||
if (statusCodesType != null)
|
||||
{
|
||||
foreach (var member in statusCodesType.GetMembers())
|
||||
{
|
||||
if (member is IFieldSymbol field &&
|
||||
field.Type.SpecialType == SpecialType.System_Int32 &&
|
||||
field.Name.StartsWith("Status") &&
|
||||
field.HasConstantValue &&
|
||||
field.ConstantValue is int statusCode)
|
||||
{
|
||||
statusCodeConstants[statusCode] = field.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return statusCodeConstants;
|
||||
}
|
||||
|
||||
private ICollection<int> CalculateStatusCodesToApply(CodeActionContext context, IList<DeclaredApiResponseMetadata> declaredResponseMetadata)
|
||||
{
|
||||
if (!SymbolApiResponseMetadataProvider.TryGetActualResponseMetadata(context.SymbolCache, context.SemanticModel, context.MethodSyntax, context.CancellationToken, out var actualResponseMetadata))
|
||||
|
|
@ -105,14 +149,31 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers
|
|||
return statusCodes;
|
||||
}
|
||||
|
||||
private static AttributeSyntax CreateProducesResponseTypeAttribute(int statusCode)
|
||||
private static AttributeSyntax CreateProducesResponseTypeAttribute(CodeActionContext context, int statusCode, out bool addUsingDirective)
|
||||
{
|
||||
var statusCodeSyntax = CreateStatusCodeSyntax(context, statusCode, out addUsingDirective);
|
||||
|
||||
return SyntaxFactory.Attribute(
|
||||
SyntaxFactory.ParseName(ApiSymbolNames.ProducesResponseTypeAttribute)
|
||||
.WithAdditionalAnnotations(Simplifier.Annotation),
|
||||
SyntaxFactory.AttributeArgumentList().AddArguments(
|
||||
SyntaxFactory.AttributeArgument(
|
||||
SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(statusCode)))));
|
||||
SyntaxFactory.AttributeArgument(statusCodeSyntax)));
|
||||
}
|
||||
|
||||
private static ExpressionSyntax CreateStatusCodeSyntax(CodeActionContext context, int statusCode, out bool addUsingDirective)
|
||||
{
|
||||
if (context.StatusCodeConstants.TryGetValue(statusCode, out var constantName))
|
||||
{
|
||||
addUsingDirective = true;
|
||||
return SyntaxFactory.MemberAccessExpression(
|
||||
SyntaxKind.SimpleMemberAccessExpression,
|
||||
SyntaxFactory.ParseTypeName(ApiSymbolNames.HttpStatusCodes)
|
||||
.WithAdditionalAnnotations(Simplifier.Annotation),
|
||||
SyntaxFactory.IdentifierName(constantName));
|
||||
}
|
||||
|
||||
addUsingDirective = false;
|
||||
return SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(statusCode));
|
||||
}
|
||||
|
||||
private static AttributeSyntax CreateProducesDefaultResponseTypeAttribute()
|
||||
|
|
@ -124,22 +185,25 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers
|
|||
|
||||
private readonly struct CodeActionContext
|
||||
{
|
||||
public CodeActionContext(
|
||||
SemanticModel semanticModel,
|
||||
public CodeActionContext(SemanticModel semanticModel,
|
||||
ApiControllerSymbolCache symbolCache,
|
||||
IMethodSymbol method,
|
||||
MethodDeclarationSyntax methodSyntax,
|
||||
Dictionary<int, string> statusCodeConstants,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
SemanticModel = semanticModel;
|
||||
SymbolCache = symbolCache;
|
||||
Method = method;
|
||||
MethodSyntax = methodSyntax;
|
||||
StatusCodeConstants = statusCodeConstants;
|
||||
CancellationToken = cancellationToken;
|
||||
}
|
||||
|
||||
public MethodDeclarationSyntax MethodSyntax { get; }
|
||||
|
||||
public Dictionary<int, string> StatusCodeConstants { get; }
|
||||
|
||||
public IMethodSymbol Method { get; }
|
||||
|
||||
public SemanticModel SemanticModel { get; }
|
||||
|
|
|
|||
|
|
@ -32,5 +32,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers
|
|||
public const string ProducesDefaultResponseTypeAttribute = "Microsoft.AspNetCore.Mvc.ProducesDefaultResponseTypeAttribute";
|
||||
|
||||
public const string ProducesResponseTypeAttribute = "Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute";
|
||||
|
||||
public const string HttpStatusCodes = "Microsoft.AspNetCore.Http.StatusCodes";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
[assembly: Microsoft.AspNetCore.Mvc.ApiConventionType(typeof(Microsoft.AspNetCore.Mvc.DefaultApiConventions))]
|
||||
|
||||
namespace TestApp._OUTPUT_
|
||||
|
|
@ -17,9 +18,9 @@ namespace TestApp._OUTPUT_
|
|||
{
|
||||
public class CodeFixAddsFullyQualifiedProducesResponseType : BaseController
|
||||
{
|
||||
[Microsoft.AspNetCore.Mvc.ProducesResponseType(202)]
|
||||
[Microsoft.AspNetCore.Mvc.ProducesResponseType(400)]
|
||||
[Microsoft.AspNetCore.Mvc.ProducesResponseType(404)]
|
||||
[Microsoft.AspNetCore.Mvc.ProducesResponseType(StatusCodes.Status202Accepted)]
|
||||
[Microsoft.AspNetCore.Mvc.ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[Microsoft.AspNetCore.Mvc.ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[Microsoft.AspNetCore.Mvc.ProducesDefaultResponseType]
|
||||
public object GetItem(int id)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._INPUT_
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._INPUT_
|
||||
{
|
||||
[ApiController]
|
||||
[Route("[controller]/[action]")]
|
||||
public class CodeFixAddsMissingStatusCodes : ControllerBase
|
||||
{
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public IActionResult GetItem(int id)
|
||||
{
|
||||
if (id == 0)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_
|
||||
{
|
||||
[ApiController]
|
||||
[Route("[controller]/[action]")]
|
||||
public class CodeFixAddsMissingStatusCodes : ControllerBase
|
||||
{
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesDefaultResponseType]
|
||||
public IActionResult GetItem(int id)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_
|
||||
{
|
||||
[ApiController]
|
||||
[Route("[controller]/[action]")]
|
||||
public class CodeFixAddsStatusCodesController : ControllerBase
|
||||
{
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public IActionResult GetItem(int id)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
[assembly: ApiConventionType(typeof(DefaultApiConventions))]
|
||||
|
||||
|
|
@ -8,9 +9,9 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_
|
|||
[Route("[controller]/[action]")]
|
||||
public class CodeFixAddsSuccessStatusCode : ControllerBase
|
||||
{
|
||||
[ProducesResponseType(201)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public ActionResult<object> GetItem(string id)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
[assembly: ApiConventionType(typeof(DefaultApiConventions))]
|
||||
|
||||
|
|
@ -8,8 +9,8 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_
|
|||
[Route("[controller]/[action]")]
|
||||
public class CodeFixWithConventionAddsMissingStatusCodes : ControllerBase
|
||||
{
|
||||
[ProducesResponseType(202)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(StatusCodes.Status202Accepted)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public ActionResult<string> GetItem(int id)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_
|
||||
{
|
||||
[ApiController]
|
||||
[Route("[controller]/[action]")]
|
||||
public class CodeFixWithConventionMethodAddsMissingStatusCodes : ControllerBase
|
||||
{
|
||||
[ProducesResponseType(202)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(StatusCodes.Status202Accepted)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesDefaultResponseType]
|
||||
public ActionResult<string> GetItem(int id)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue