Add a code fix that applies ProducesResponseTypeAttributes
This commit is contained in:
parent
2b289d2f2c
commit
b7335ac768
|
|
@ -0,0 +1,152 @@
|
|||
// 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.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CodeActions;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Editing;
|
||||
using Microsoft.CodeAnalysis.Simplification;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Analyzers
|
||||
{
|
||||
internal sealed class AddResponseTypeAttributeCodeFixAction : CodeAction
|
||||
{
|
||||
private readonly Document _document;
|
||||
private readonly Diagnostic _diagnostic;
|
||||
|
||||
public AddResponseTypeAttributeCodeFixAction(Document document, Diagnostic diagnostic)
|
||||
{
|
||||
_document = document;
|
||||
_diagnostic = diagnostic;
|
||||
}
|
||||
|
||||
public override string Title => "Add ProducesResponseType attributes.";
|
||||
|
||||
protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var context = await CreateCodeActionContext(cancellationToken).ConfigureAwait(false);
|
||||
var declaredResponseMetadata = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(context.SymbolCache, context.Method);
|
||||
|
||||
var statusCodes = CalculateStatusCodesToApply(context, declaredResponseMetadata);
|
||||
if (statusCodes.Count == 0)
|
||||
{
|
||||
return _document;
|
||||
}
|
||||
|
||||
var documentEditor = await DocumentEditor.CreateAsync(_document, cancellationToken).ConfigureAwait(false);
|
||||
foreach (var statusCode in statusCodes.OrderBy(s => s))
|
||||
{
|
||||
documentEditor.AddAttribute(context.MethodSyntax, CreateProducesResponseTypeAttribute(statusCode));
|
||||
}
|
||||
|
||||
if (!declaredResponseMetadata.Any(m => m.IsDefault && m.AttributeSource == context.Method))
|
||||
{
|
||||
// Add a ProducesDefaultResponseTypeAttribute if the method does not already have one.
|
||||
documentEditor.AddAttribute(context.MethodSyntax, CreateProducesDefaultResponseTypeAttribute());
|
||||
}
|
||||
|
||||
var apiConventionMethodAttribute = context.Method.GetAttributes(context.SymbolCache.ApiConventionMethodAttribute).FirstOrDefault();
|
||||
|
||||
if (apiConventionMethodAttribute != null)
|
||||
{
|
||||
// Remove [ApiConventionMethodAttribute] declared on the method since it's no longer required
|
||||
var attributeSyntax = await apiConventionMethodAttribute
|
||||
.ApplicationSyntaxReference
|
||||
.GetSyntaxAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
documentEditor.RemoveNode(attributeSyntax);
|
||||
}
|
||||
|
||||
return documentEditor.GetChangedDocument();
|
||||
}
|
||||
|
||||
private async Task<CodeActionContext> CreateCodeActionContext(CancellationToken cancellationToken)
|
||||
{
|
||||
var root = await _document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
|
||||
var semanticModel = await _document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
|
||||
var methodReturnStatement = (ReturnStatementSyntax)root.FindNode(_diagnostic.Location.SourceSpan);
|
||||
var methodSyntax = methodReturnStatement.FirstAncestorOrSelf<MethodDeclarationSyntax>();
|
||||
var method = semanticModel.GetDeclaredSymbol(methodSyntax, cancellationToken);
|
||||
|
||||
var symbolCache = new ApiControllerSymbolCache(semanticModel.Compilation);
|
||||
|
||||
var codeActionContext = new CodeActionContext(semanticModel, symbolCache, method, methodSyntax, cancellationToken);
|
||||
return codeActionContext;
|
||||
}
|
||||
|
||||
private ICollection<int> CalculateStatusCodesToApply(CodeActionContext context, IList<DeclaredApiResponseMetadata> declaredResponseMetadata)
|
||||
{
|
||||
if (!SymbolApiResponseMetadataProvider.TryGetActualResponseMetadata(context.SymbolCache, context.SemanticModel, context.MethodSyntax, context.CancellationToken, out var actualResponseMetadata))
|
||||
{
|
||||
// If we cannot parse metadata correctly, don't offer fixes.
|
||||
return Array.Empty<int>();
|
||||
}
|
||||
|
||||
var statusCodes = new HashSet<int>();
|
||||
foreach (var metadata in actualResponseMetadata)
|
||||
{
|
||||
if (DeclaredApiResponseMetadata.TryGetDeclaredMetadata(declaredResponseMetadata, metadata, result: out var declaredMetadata) &&
|
||||
declaredMetadata.AttributeSource == context.Method)
|
||||
{
|
||||
// A ProducesResponseType attribute is declared on the method for the current status code.
|
||||
continue;
|
||||
}
|
||||
|
||||
statusCodes.Add(metadata.IsDefaultResponse ? 200 : metadata.StatusCode);
|
||||
}
|
||||
|
||||
return statusCodes;
|
||||
}
|
||||
|
||||
private static AttributeSyntax CreateProducesResponseTypeAttribute(int statusCode)
|
||||
{
|
||||
return SyntaxFactory.Attribute(
|
||||
SyntaxFactory.ParseName(SymbolNames.ProducesResponseTypeAttribute)
|
||||
.WithAdditionalAnnotations(Simplifier.Annotation),
|
||||
SyntaxFactory.AttributeArgumentList().AddArguments(
|
||||
SyntaxFactory.AttributeArgument(
|
||||
SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(statusCode)))));
|
||||
}
|
||||
|
||||
private static AttributeSyntax CreateProducesDefaultResponseTypeAttribute()
|
||||
{
|
||||
return SyntaxFactory.Attribute(
|
||||
SyntaxFactory.ParseName(SymbolNames.ProducesDefaultResponseTypeAttribute)
|
||||
.WithAdditionalAnnotations(Simplifier.Annotation));
|
||||
}
|
||||
|
||||
private readonly struct CodeActionContext
|
||||
{
|
||||
public CodeActionContext(
|
||||
SemanticModel semanticModel,
|
||||
ApiControllerSymbolCache symbolCache,
|
||||
IMethodSymbol method,
|
||||
MethodDeclarationSyntax methodSyntax,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
SemanticModel = semanticModel;
|
||||
SymbolCache = symbolCache;
|
||||
Method = method;
|
||||
MethodSyntax = methodSyntax;
|
||||
CancellationToken = cancellationToken;
|
||||
}
|
||||
|
||||
public MethodDeclarationSyntax MethodSyntax { get; }
|
||||
|
||||
public IMethodSymbol Method { get; }
|
||||
|
||||
public SemanticModel SemanticModel { get; }
|
||||
|
||||
public ApiControllerSymbolCache SymbolCache { get; }
|
||||
|
||||
public CancellationToken CancellationToken { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
// 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 System.Composition;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CodeFixes;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Analyzers
|
||||
{
|
||||
[ExportCodeFixProvider(LanguageNames.CSharp)]
|
||||
[Shared]
|
||||
public class AddResponseTypeAttributeCodeFixProvider : CodeFixProvider
|
||||
{
|
||||
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(
|
||||
DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode.Id,
|
||||
DiagnosticDescriptors.MVC1005_ActionReturnsUndocumentedSuccessResult.Id);
|
||||
|
||||
public sealed override Task RegisterCodeFixesAsync(CodeFixContext context)
|
||||
{
|
||||
if (context.Diagnostics.Length == 0)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var diagnostic = context.Diagnostics[0];
|
||||
if ((diagnostic.Descriptor.Id != DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode.Id) &&
|
||||
(diagnostic.Descriptor.Id != DiagnosticDescriptors.MVC1005_ActionReturnsUndocumentedSuccessResult.Id))
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var codeFix = new AddResponseTypeAttributeCodeFixAction(context.Document, diagnostic);
|
||||
|
||||
context.RegisterCodeFix(codeFix, diagnostic);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
ModelStateDictionary = compilation.GetTypeByMetadataName(SymbolNames.ModelStateDictionary);
|
||||
NonActionAttribute = compilation.GetTypeByMetadataName(SymbolNames.NonActionAttribute);
|
||||
NonControllerAttribute = compilation.GetTypeByMetadataName(SymbolNames.NonControllerAttribute);
|
||||
ProducesDefaultResponseTypeAttribute = compilation.GetTypeByMetadataName(SymbolNames.ProducesDefaultResponseTypeAttribute);
|
||||
ProducesResponseTypeAttribute = compilation.GetTypeByMetadataName(SymbolNames.ProducesResponseTypeAttribute);
|
||||
|
||||
var disposable = compilation.GetSpecialType(SpecialType.System_IDisposable);
|
||||
|
|
@ -58,6 +59,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
|
||||
public INamedTypeSymbol NonControllerAttribute { get; }
|
||||
|
||||
public INamedTypeSymbol ProducesDefaultResponseTypeAttribute { get; }
|
||||
|
||||
public INamedTypeSymbol ProducesResponseTypeAttribute { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
|
@ -51,34 +50,32 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
return;
|
||||
}
|
||||
|
||||
var conventionAttributes = GetConventionTypeAttributes(symbolCache, method);
|
||||
var declaredResponseMetadata = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, conventionAttributes);
|
||||
var declaredResponseMetadata = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);
|
||||
var hasUnreadableStatusCodes = !SymbolApiResponseMetadataProvider.TryGetActualResponseMetadata(symbolCache, semanticModel, methodSyntax, cancellationToken, out var actualResponseMetadata);
|
||||
|
||||
var hasUnreadableStatusCodes = SymbolApiResponseMetadataProvider.TryGetActualResponseMetadata(symbolCache, semanticModel, methodSyntax, cancellationToken, out var actualResponseMetadata);
|
||||
var hasUndocumentedStatusCodes = false;
|
||||
foreach (var item in actualResponseMetadata)
|
||||
foreach (var actualMetadata in actualResponseMetadata)
|
||||
{
|
||||
var location = item.ReturnStatement.GetLocation();
|
||||
var location = actualMetadata.ReturnStatement.GetLocation();
|
||||
|
||||
if (item.IsDefaultResponse)
|
||||
if (!DeclaredApiResponseMetadata.Contains(declaredResponseMetadata, actualMetadata))
|
||||
{
|
||||
if (!(HasStatusCode(declaredResponseMetadata, 200) || HasStatusCode(declaredResponseMetadata, 201)))
|
||||
hasUndocumentedStatusCodes = true;
|
||||
if (actualMetadata.IsDefaultResponse)
|
||||
{
|
||||
hasUndocumentedStatusCodes = true;
|
||||
syntaxNodeContext.ReportDiagnostic(Diagnostic.Create(
|
||||
DiagnosticDescriptors.MVC1005_ActionReturnsUndocumentedSuccessResult,
|
||||
location));
|
||||
}
|
||||
}
|
||||
else if (!HasStatusCode(declaredResponseMetadata, item.StatusCode))
|
||||
else
|
||||
{
|
||||
hasUndocumentedStatusCodes = true;
|
||||
syntaxNodeContext.ReportDiagnostic(Diagnostic.Create(
|
||||
DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode,
|
||||
location,
|
||||
item.StatusCode));
|
||||
actualMetadata.StatusCode));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasUndocumentedStatusCodes || hasUnreadableStatusCodes)
|
||||
{
|
||||
|
|
@ -89,59 +86,24 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
|
||||
for (var i = 0; i < declaredResponseMetadata.Count; i++)
|
||||
{
|
||||
var expectedStatusCode = declaredResponseMetadata[i].StatusCode;
|
||||
if (!HasStatusCode(actualResponseMetadata, expectedStatusCode))
|
||||
var declaredMetadata = declaredResponseMetadata[i];
|
||||
if (!Contains(actualResponseMetadata, declaredMetadata))
|
||||
{
|
||||
syntaxNodeContext.ReportDiagnostic(Diagnostic.Create(
|
||||
DiagnosticDescriptors.MVC1006_ActionDoesNotReturnDocumentedStatusCode,
|
||||
methodSyntax.Identifier.GetLocation(),
|
||||
expectedStatusCode));
|
||||
declaredMetadata.StatusCode));
|
||||
}
|
||||
}
|
||||
|
||||
}, SyntaxKind.MethodDeclaration);
|
||||
}
|
||||
|
||||
internal IReadOnlyList<AttributeData> GetConventionTypeAttributes(ApiControllerSymbolCache symbolCache, IMethodSymbol method)
|
||||
{
|
||||
var attributes = method.ContainingType.GetAttributes(symbolCache.ApiConventionTypeAttribute).ToArray();
|
||||
if (attributes.Length == 0)
|
||||
{
|
||||
attributes = method.ContainingAssembly.GetAttributes(symbolCache.ApiConventionTypeAttribute).ToArray();
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
internal static bool HasStatusCode(IList<DeclaredApiResponseMetadata> declaredApiResponseMetadata, int statusCode)
|
||||
{
|
||||
if (declaredApiResponseMetadata.Count == 0)
|
||||
{
|
||||
// When no status code is declared, a 200 OK is implied.
|
||||
return statusCode == 200;
|
||||
}
|
||||
|
||||
for (var i = 0; i < declaredApiResponseMetadata.Count; i++)
|
||||
{
|
||||
if (declaredApiResponseMetadata[i].StatusCode == statusCode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool HasStatusCode(IList<ActualApiResponseMetadata> actualResponseMetadata, int statusCode)
|
||||
internal static bool Contains(IList<ActualApiResponseMetadata> actualResponseMetadata, DeclaredApiResponseMetadata declaredMetadata)
|
||||
{
|
||||
for (var i = 0; i < actualResponseMetadata.Count; i++)
|
||||
{
|
||||
if (actualResponseMetadata[i].IsDefaultResponse)
|
||||
{
|
||||
return statusCode == 200 || statusCode == 201;
|
||||
}
|
||||
|
||||
else if(actualResponseMetadata[i].StatusCode == statusCode)
|
||||
if (declaredMetadata.Matches(actualResponseMetadata[i]))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,92 @@
|
|||
// 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 Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Analyzers
|
||||
{
|
||||
internal readonly struct DeclaredApiResponseMetadata
|
||||
{
|
||||
public DeclaredApiResponseMetadata(int statusCode, AttributeData attributeData, IMethodSymbol convention)
|
||||
public static DeclaredApiResponseMetadata ImplicitResponse { get; } =
|
||||
new DeclaredApiResponseMetadata(statusCode: 0, attributeData: null, attributeSource: null, @implicit: true, @default: false);
|
||||
|
||||
public static DeclaredApiResponseMetadata ForProducesResponseType(int statusCode, AttributeData attributeData, IMethodSymbol attributeSource)
|
||||
{
|
||||
return new DeclaredApiResponseMetadata(statusCode, attributeData, attributeSource, @implicit: false, @default: false);
|
||||
}
|
||||
|
||||
public static DeclaredApiResponseMetadata ForProducesDefaultResponse(AttributeData attributeData, IMethodSymbol attributeSource)
|
||||
{
|
||||
return new DeclaredApiResponseMetadata(statusCode: 0, attributeData, attributeSource, @implicit: false, @default: true);
|
||||
}
|
||||
|
||||
private DeclaredApiResponseMetadata(
|
||||
int statusCode,
|
||||
AttributeData attributeData,
|
||||
IMethodSymbol attributeSource,
|
||||
bool @implicit,
|
||||
bool @default)
|
||||
{
|
||||
StatusCode = statusCode;
|
||||
Attribute = attributeData;
|
||||
Convention = convention;
|
||||
AttributeSource = attributeSource;
|
||||
IsImplicit = @implicit;
|
||||
IsDefault = @default;
|
||||
}
|
||||
|
||||
public int StatusCode { get; }
|
||||
|
||||
public AttributeData Attribute { get; }
|
||||
|
||||
public IMethodSymbol Convention { get; }
|
||||
public IMethodSymbol AttributeSource { get; }
|
||||
|
||||
public bool IsImplicit { get; }
|
||||
|
||||
public bool IsDefault { get; }
|
||||
|
||||
internal static bool Contains(IList<DeclaredApiResponseMetadata> declaredApiResponseMetadata, ActualApiResponseMetadata actualMetadata)
|
||||
{
|
||||
return TryGetDeclaredMetadata(declaredApiResponseMetadata, actualMetadata, out _);
|
||||
}
|
||||
|
||||
internal static bool TryGetDeclaredMetadata(
|
||||
IList<DeclaredApiResponseMetadata> declaredApiResponseMetadata,
|
||||
ActualApiResponseMetadata actualMetadata,
|
||||
out DeclaredApiResponseMetadata result)
|
||||
{
|
||||
for (var i = 0; i < declaredApiResponseMetadata.Count; i++)
|
||||
{
|
||||
var declaredMetadata = declaredApiResponseMetadata[i];
|
||||
|
||||
if (declaredMetadata.Matches(actualMetadata))
|
||||
{
|
||||
result = declaredMetadata;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
internal bool Matches(ActualApiResponseMetadata actualMetadata)
|
||||
{
|
||||
if (actualMetadata.IsDefaultResponse)
|
||||
{
|
||||
return IsImplicit || StatusCode == 200 || StatusCode == 201;
|
||||
}
|
||||
else if (actualMetadata.StatusCode == StatusCode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (actualMetadata.StatusCode >= 400 && IsDefault)
|
||||
{
|
||||
// ProducesDefaultResponse matches any failure code
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,11 +16,14 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
private const string StatusCodeProperty = "StatusCode";
|
||||
private const string StatusCodeConstructorParameter = "statusCode";
|
||||
private static readonly Func<SyntaxNode, bool> _shouldDescendIntoChildren = ShouldDescendIntoChildren;
|
||||
private static readonly IList<DeclaredApiResponseMetadata> DefaultResponseMetadatas = new[]
|
||||
{
|
||||
DeclaredApiResponseMetadata.ImplicitResponse,
|
||||
};
|
||||
|
||||
internal static IList<DeclaredApiResponseMetadata> GetDeclaredResponseMetadata(
|
||||
ApiControllerSymbolCache symbolCache,
|
||||
IMethodSymbol method,
|
||||
IReadOnlyList<AttributeData> conventionTypeAttributes)
|
||||
IMethodSymbol method)
|
||||
{
|
||||
var metadataItems = GetResponseMetadataFromMethodAttributes(symbolCache, method);
|
||||
if (metadataItems.Count != 0)
|
||||
|
|
@ -28,19 +31,28 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
return metadataItems;
|
||||
}
|
||||
|
||||
var conventionTypeAttributes = GetConventionTypes(symbolCache, method);
|
||||
metadataItems = GetResponseMetadataFromConventions(symbolCache, method, conventionTypeAttributes);
|
||||
|
||||
if (metadataItems.Count == 0)
|
||||
{
|
||||
// If no metadata can be gleaned either through explicit attributes on the method or via a convention,
|
||||
// declare an implicit 200 status code.
|
||||
metadataItems = DefaultResponseMetadatas;
|
||||
}
|
||||
|
||||
return metadataItems;
|
||||
}
|
||||
|
||||
private static IList<DeclaredApiResponseMetadata> GetResponseMetadataFromConventions(
|
||||
ApiControllerSymbolCache symbolCache,
|
||||
IMethodSymbol method,
|
||||
IReadOnlyList<AttributeData> attributes)
|
||||
IReadOnlyList<ITypeSymbol> conventionTypes)
|
||||
{
|
||||
var conventionMethod = GetMethodFromConventionMethodAttribute(symbolCache, method);
|
||||
if (conventionMethod == null)
|
||||
{
|
||||
conventionMethod = MatchConventionMethod(symbolCache, method, attributes);
|
||||
conventionMethod = MatchConventionMethod(symbolCache, method, conventionTypes);
|
||||
}
|
||||
|
||||
if (conventionMethod != null)
|
||||
|
|
@ -49,8 +61,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
}
|
||||
|
||||
return Array.Empty<DeclaredApiResponseMetadata>();
|
||||
}
|
||||
|
||||
}
|
||||
private static IMethodSymbol GetMethodFromConventionMethodAttribute(ApiControllerSymbolCache symbolCache, IMethodSymbol method)
|
||||
{
|
||||
var attribute = method.GetAttributes(symbolCache.ApiConventionMethodAttribute, inherit: true)
|
||||
|
|
@ -87,17 +98,10 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
private static IMethodSymbol MatchConventionMethod(
|
||||
ApiControllerSymbolCache symbolCache,
|
||||
IMethodSymbol method,
|
||||
IReadOnlyList<AttributeData> attributes)
|
||||
IReadOnlyList<ITypeSymbol> conventionTypes)
|
||||
{
|
||||
foreach (var attribute in attributes)
|
||||
foreach (var conventionType in conventionTypes)
|
||||
{
|
||||
if (attribute.ConstructorArguments.Length != 1 ||
|
||||
attribute.ConstructorArguments[0].Kind != TypedConstantKind.Type ||
|
||||
!(attribute.ConstructorArguments[0].Value is ITypeSymbol conventionType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var conventionMethod in conventionType.GetMembers().OfType<IMethodSymbol>())
|
||||
{
|
||||
if (!conventionMethod.IsStatic || conventionMethod.DeclaredAccessibility != Accessibility.Public)
|
||||
|
|
@ -122,14 +126,45 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
foreach (var attribute in responseMetadataAttributes)
|
||||
{
|
||||
var statusCode = GetStatusCode(attribute);
|
||||
var metadata = new DeclaredApiResponseMetadata(statusCode, attribute, convention: null);
|
||||
var metadata = DeclaredApiResponseMetadata.ForProducesResponseType(statusCode, attribute, attributeSource: methodSymbol);
|
||||
|
||||
metadataItems.Add(metadata);
|
||||
}
|
||||
|
||||
var producesDefaultResponse = methodSymbol.GetAttributes(symbolCache.ProducesDefaultResponseTypeAttribute, inherit: true).FirstOrDefault();
|
||||
if (producesDefaultResponse != null)
|
||||
{
|
||||
metadataItems.Add(DeclaredApiResponseMetadata.ForProducesDefaultResponse(producesDefaultResponse, methodSymbol));
|
||||
}
|
||||
|
||||
return metadataItems;
|
||||
}
|
||||
|
||||
internal static IReadOnlyList<ITypeSymbol> GetConventionTypes(ApiControllerSymbolCache symbolCache, IMethodSymbol method)
|
||||
{
|
||||
var attributes = method.ContainingType.GetAttributes(symbolCache.ApiConventionTypeAttribute).ToArray();
|
||||
if (attributes.Length == 0)
|
||||
{
|
||||
attributes = method.ContainingAssembly.GetAttributes(symbolCache.ApiConventionTypeAttribute).ToArray();
|
||||
}
|
||||
|
||||
var conventionTypes = new List<ITypeSymbol>();
|
||||
for (var i = 0; i < attributes.Length; i++)
|
||||
{
|
||||
var attribute = attributes[i];
|
||||
if (attribute.ConstructorArguments.Length != 1 ||
|
||||
attribute.ConstructorArguments[0].Kind != TypedConstantKind.Type ||
|
||||
!(attribute.ConstructorArguments[0].Value is ITypeSymbol conventionType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
conventionTypes.Add(conventionType);
|
||||
}
|
||||
|
||||
return conventionTypes;
|
||||
}
|
||||
|
||||
internal static int GetStatusCode(AttributeData attribute)
|
||||
{
|
||||
const int DefaultStatusCode = 200;
|
||||
|
|
@ -183,7 +218,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
{
|
||||
actualResponseMetadata = new List<ActualApiResponseMetadata>();
|
||||
|
||||
var hasUnreadableReturnStatements = false;
|
||||
var allReturnStatementsReadable = true;
|
||||
|
||||
foreach (var returnStatementSyntax in methodSyntax.DescendantNodes(_shouldDescendIntoChildren).OfType<ReturnStatementSyntax>())
|
||||
{
|
||||
|
|
@ -199,11 +234,11 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
}
|
||||
else
|
||||
{
|
||||
hasUnreadableReturnStatements = true;
|
||||
allReturnStatementsReadable = false;
|
||||
}
|
||||
}
|
||||
|
||||
return hasUnreadableReturnStatements;
|
||||
return allReturnStatementsReadable;
|
||||
}
|
||||
|
||||
internal static ActualApiResponseMetadata? InspectReturnStatementSyntax(
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
|
||||
public const string PartialMethod = "Partial";
|
||||
|
||||
public const string ProducesDefaultResponseTypeAttribute = "Microsoft.AspNetCore.Mvc.ProducesDefaultResponseTypeAttribute";
|
||||
|
||||
public const string ProducesResponseTypeAttribute = "Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute";
|
||||
|
||||
public const string RenderPartialMethod = "RenderPartial";
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
// 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.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Analyzer.Testing;
|
||||
using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Analyzers
|
||||
{
|
||||
public class AddResponseTypeAttributeCodeFixProviderIntegrationTest
|
||||
{
|
||||
private MvcDiagnosticAnalyzerRunner AnalyzerRunner { get; } = new MvcDiagnosticAnalyzerRunner(new ApiConventionAnalyzer());
|
||||
|
||||
private CodeFixRunner CodeFixRunner => CodeFixRunner.Default;
|
||||
|
||||
[Fact]
|
||||
public Task CodeFixAddsStatusCodes() => RunTest();
|
||||
|
||||
[Fact]
|
||||
public Task CodeFixAddsMissingStatusCodes() => RunTest();
|
||||
|
||||
[Fact]
|
||||
public Task CodeFixWithConventionAddsMissingStatusCodes() => RunTest();
|
||||
|
||||
[Fact]
|
||||
public Task CodeFixWithConventionMethodAddsMissingStatusCodes() => RunTest();
|
||||
|
||||
[Fact]
|
||||
public Task CodeFixAddsSuccessStatusCode() => RunTest();
|
||||
|
||||
[Fact]
|
||||
public Task CodeFixAddsFullyQualifiedProducesResponseType() => RunTest();
|
||||
|
||||
private async Task RunTest([CallerMemberName] string testMethod = "")
|
||||
{
|
||||
// Arrange
|
||||
var project = GetProject(testMethod);
|
||||
var controllerDocument = project.DocumentIds[0];
|
||||
|
||||
var expectedOutput = Read(testMethod + ".Output");
|
||||
|
||||
// Act
|
||||
var diagnostics = await AnalyzerRunner.GetDiagnosticsAsync(project);
|
||||
var actualOutput = await CodeFixRunner.ApplyCodeFixAsync(
|
||||
new AddResponseTypeAttributeCodeFixProvider(),
|
||||
project.GetDocument(controllerDocument),
|
||||
diagnostics[0]);
|
||||
|
||||
Assert.Equal(expectedOutput, actualOutput, ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
private Project GetProject(string testMethod)
|
||||
{
|
||||
var testSource = Read(testMethod + ".Input");
|
||||
return DiagnosticProject.Create(GetType().Assembly, new[] { testSource });
|
||||
}
|
||||
|
||||
private string Read(string fileName)
|
||||
{
|
||||
return MvcTestSource.Read(GetType().Name, fileName)
|
||||
.Source
|
||||
.Replace("_INPUT_", "_TEST_")
|
||||
.Replace("_OUTPUT_", "_TEST_");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
// 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.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
|
@ -29,10 +27,12 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
var symbolCache = new ApiControllerSymbolCache(compilation);
|
||||
|
||||
// Act
|
||||
var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty<AttributeData>());
|
||||
var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
Assert.Collection(
|
||||
result,
|
||||
metadata => Assert.True(metadata.IsImplicit));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -45,10 +45,12 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
var symbolCache = new ApiControllerSymbolCache(compilation);
|
||||
|
||||
// Act
|
||||
var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty<AttributeData>());
|
||||
var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
Assert.Collection(
|
||||
result,
|
||||
metadata => Assert.True(metadata.IsImplicit));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -61,10 +63,12 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
var symbolCache = new ApiControllerSymbolCache(compilation);
|
||||
|
||||
// Act
|
||||
var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty<AttributeData>());
|
||||
var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
Assert.Collection(
|
||||
result,
|
||||
metadata => Assert.True(metadata.IsImplicit));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -77,7 +81,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
var symbolCache = new ApiControllerSymbolCache(compilation);
|
||||
|
||||
// Act
|
||||
var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty<AttributeData>());
|
||||
var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
|
|
@ -86,7 +90,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
{
|
||||
Assert.Equal(201, metadata.StatusCode);
|
||||
Assert.NotNull(metadata.Attribute);
|
||||
Assert.Null(metadata.Convention);
|
||||
Assert.Equal(method, metadata.AttributeSource);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -100,7 +104,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
var symbolCache = new ApiControllerSymbolCache(compilation);
|
||||
|
||||
// Act
|
||||
var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty<AttributeData>());
|
||||
var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
|
|
@ -109,7 +113,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
{
|
||||
Assert.Equal(202, metadata.StatusCode);
|
||||
Assert.NotNull(metadata.Attribute);
|
||||
Assert.Null(metadata.Convention);
|
||||
Assert.Equal(method, metadata.AttributeSource);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -123,7 +127,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
var symbolCache = new ApiControllerSymbolCache(compilation);
|
||||
|
||||
// Act
|
||||
var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty<AttributeData>());
|
||||
var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
|
|
@ -132,7 +136,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
{
|
||||
Assert.Equal(203, metadata.StatusCode);
|
||||
Assert.NotNull(metadata.Attribute);
|
||||
Assert.Null(metadata.Convention);
|
||||
Assert.Equal(method, metadata.AttributeSource);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -146,7 +150,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
var symbolCache = new ApiControllerSymbolCache(compilation);
|
||||
|
||||
// Act
|
||||
var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty<AttributeData>());
|
||||
var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
|
|
@ -155,7 +159,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
{
|
||||
Assert.Equal(201, metadata.StatusCode);
|
||||
Assert.NotNull(metadata.Attribute);
|
||||
Assert.Null(metadata.Convention);
|
||||
Assert.Equal(method, metadata.AttributeSource);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -169,7 +173,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
var symbolCache = new ApiControllerSymbolCache(compilation);
|
||||
|
||||
// Act
|
||||
var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty<AttributeData>());
|
||||
var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
|
|
@ -178,7 +182,6 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
{
|
||||
Assert.Equal(201, metadata.StatusCode);
|
||||
Assert.NotNull(metadata.Attribute);
|
||||
Assert.Null(metadata.Convention);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -192,7 +195,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
var symbolCache = new ApiControllerSymbolCache(compilation);
|
||||
|
||||
// Act
|
||||
var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty<AttributeData>());
|
||||
var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
|
|
@ -206,6 +209,10 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
{
|
||||
Assert.Equal(404, metadata.StatusCode);
|
||||
Assert.NotNull(metadata.Attribute);
|
||||
},
|
||||
metadata =>
|
||||
{
|
||||
Assert.True(metadata.IsDefault);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -219,7 +226,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
var symbolCache = new ApiControllerSymbolCache(compilation);
|
||||
|
||||
// Act
|
||||
var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty<AttributeData>());
|
||||
var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
|
|
@ -241,16 +248,18 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
var symbolCache = new ApiControllerSymbolCache(compilation);
|
||||
|
||||
// Act
|
||||
var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty<AttributeData>());
|
||||
var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
Assert.Collection(
|
||||
result,
|
||||
metadata => Assert.True(metadata.IsImplicit));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task GetResponseMetadata_IgnoresAttributesWithIncorrectStatusCodeType()
|
||||
{
|
||||
return GetResponseMetadata_IgnoresInvalidOrUnsupportedAttribues(
|
||||
return GetResponseMetadata_WorksForInvalidOrUnsupportedAttribues(
|
||||
nameof(GetResponseMetadata_ControllerActionWithAttributes),
|
||||
nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithProducesResponseTypeWithIncorrectStatusCodeType));
|
||||
}
|
||||
|
|
@ -258,12 +267,12 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
[Fact]
|
||||
public Task GetResponseMetadata_IgnoresDerivedAttributesWithoutPropertyOnConstructorArguments()
|
||||
{
|
||||
return GetResponseMetadata_IgnoresInvalidOrUnsupportedAttribues(
|
||||
return GetResponseMetadata_WorksForInvalidOrUnsupportedAttribues(
|
||||
nameof(GetResponseMetadata_ControllerActionWithAttributes),
|
||||
nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithCustomProducesResponseTypeAttributeWithoutArguments));
|
||||
}
|
||||
|
||||
private async Task GetResponseMetadata_IgnoresInvalidOrUnsupportedAttribues(string typeName, string methodName)
|
||||
private async Task GetResponseMetadata_WorksForInvalidOrUnsupportedAttribues(string typeName, string methodName)
|
||||
{
|
||||
// Arrange
|
||||
var compilation = await GetResponseMetadataCompilation();
|
||||
|
|
@ -272,7 +281,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
var symbolCache = new ApiControllerSymbolCache(compilation);
|
||||
|
||||
// Act
|
||||
var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty<AttributeData>());
|
||||
var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
|
|
@ -280,8 +289,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
metadata =>
|
||||
{
|
||||
Assert.Equal(200, metadata.StatusCode);
|
||||
Assert.NotNull(metadata.Attribute);
|
||||
Assert.Null(metadata.Convention);
|
||||
Assert.Same(method, metadata.AttributeSource);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
[assembly: Microsoft.AspNetCore.Mvc.ApiConventionType(typeof(Microsoft.AspNetCore.Mvc.DefaultApiConventions))]
|
||||
|
||||
namespace TestApp._INPUT_
|
||||
{
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
[ApiController]
|
||||
[Route("[controller]/[action]")]
|
||||
public class BaseController : ControllerBase
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
namespace TestApp._INPUT_
|
||||
{
|
||||
public class CodeFixAddsFullyQualifiedProducesResponseType : BaseController
|
||||
{
|
||||
public object GetItem(int id)
|
||||
{
|
||||
if (id == 0)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (id == 1)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
return Accepted(new object());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
|
||||
[assembly: Microsoft.AspNetCore.Mvc.ApiConventionType(typeof(Microsoft.AspNetCore.Mvc.DefaultApiConventions))]
|
||||
|
||||
namespace TestApp._OUTPUT_
|
||||
{
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
[ApiController]
|
||||
[Route("[controller]/[action]")]
|
||||
public class BaseController : ControllerBase
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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.ProducesDefaultResponseType]
|
||||
public object GetItem(int id)
|
||||
{
|
||||
if (id == 0)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (id == 1)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
return Accepted(new object());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
namespace Microsoft.AspNetCore.Mvc.Analyzers._INPUT_
|
||||
{
|
||||
[ApiController]
|
||||
[Route("[controller]/[action]")]
|
||||
public class CodeFixAddsMissingStatusCodes : ControllerBase
|
||||
{
|
||||
[ProducesResponseType(404)]
|
||||
public IActionResult GetItem(int id)
|
||||
{
|
||||
if (id == 0)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (id == 1)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
return Ok(new object());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
namespace Microsoft.AspNetCore.Mvc.Analyzers._OUTPUT_
|
||||
{
|
||||
[ApiController]
|
||||
[Route("[controller]/[action]")]
|
||||
public class CodeFixAddsMissingStatusCodes : ControllerBase
|
||||
{
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesDefaultResponseType]
|
||||
public IActionResult GetItem(int id)
|
||||
{
|
||||
if (id == 0)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (id == 1)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
return Ok(new object());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
namespace Microsoft.AspNetCore.Mvc.Analyzers._INPUT_
|
||||
{
|
||||
[ApiController]
|
||||
[Route("[controller]/[action]")]
|
||||
public class CodeFixAddsStatusCodesController : ControllerBase
|
||||
{
|
||||
public IActionResult GetItem(int id)
|
||||
{
|
||||
if (id == 0)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(new object());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
namespace Microsoft.AspNetCore.Mvc.Analyzers._OUTPUT_
|
||||
{
|
||||
[ApiController]
|
||||
[Route("[controller]/[action]")]
|
||||
public class CodeFixAddsStatusCodesController : ControllerBase
|
||||
{
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesDefaultResponseType]
|
||||
public IActionResult GetItem(int id)
|
||||
{
|
||||
if (id == 0)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(new object());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
[assembly: ApiConventionType(typeof(DefaultApiConventions))]
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Analyzers._INPUT_
|
||||
{
|
||||
[ApiController]
|
||||
[Route("[controller]/[action]")]
|
||||
public class CodeFixAddsSuccessStatusCode : ControllerBase
|
||||
{
|
||||
public ActionResult<object> GetItem(string id)
|
||||
{
|
||||
if (!int.TryParse(id, out var idInt))
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
if (idInt == 0)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Created("url", new object());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
[assembly: ApiConventionType(typeof(DefaultApiConventions))]
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Analyzers._OUTPUT_
|
||||
{
|
||||
[ApiController]
|
||||
[Route("[controller]/[action]")]
|
||||
public class CodeFixAddsSuccessStatusCode : ControllerBase
|
||||
{
|
||||
[ProducesResponseType(201)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesDefaultResponseType]
|
||||
public ActionResult<object> GetItem(string id)
|
||||
{
|
||||
if (!int.TryParse(id, out var idInt))
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
if (idInt == 0)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Created("url", new object());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
[assembly: ApiConventionType(typeof(DefaultApiConventions))]
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Analyzers._INPUT_
|
||||
{
|
||||
[ApiController]
|
||||
[Route("[controller]/[action]")]
|
||||
public class CodeFixWithConventionAddsMissingStatusCodes : ControllerBase
|
||||
{
|
||||
public ActionResult<string> GetItem(int id)
|
||||
{
|
||||
if (id == 0)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Accepted("Result");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
[assembly: ApiConventionType(typeof(DefaultApiConventions))]
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Analyzers._OUTPUT_
|
||||
{
|
||||
[ApiController]
|
||||
[Route("[controller]/[action]")]
|
||||
public class CodeFixWithConventionAddsMissingStatusCodes : ControllerBase
|
||||
{
|
||||
[ProducesResponseType(202)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesDefaultResponseType]
|
||||
public ActionResult<string> GetItem(int id)
|
||||
{
|
||||
if (id == 0)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Accepted("Result");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
namespace Microsoft.AspNetCore.Mvc.Analyzers._INPUT_
|
||||
{
|
||||
[ApiController]
|
||||
[Route("[controller]/[action]")]
|
||||
public class CodeFixWithConventionMethodAddsMissingStatusCodes : ControllerBase
|
||||
{
|
||||
[ApiConventionMethod(typeof(DefaultApiConventions), nameof(DefaultApiConventions.Find))]
|
||||
public ActionResult<string> GetItem(int id)
|
||||
{
|
||||
if (id == 0)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Accepted("Result");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
namespace Microsoft.AspNetCore.Mvc.Analyzers._OUTPUT_
|
||||
{
|
||||
[ApiController]
|
||||
[Route("[controller]/[action]")]
|
||||
public class CodeFixWithConventionMethodAddsMissingStatusCodes : ControllerBase
|
||||
{
|
||||
[ProducesResponseType(202)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesDefaultResponseType]
|
||||
public ActionResult<string> GetItem(int id)
|
||||
{
|
||||
if (id == 0)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Accepted("Result");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Analyzers;
|
||||
|
||||
[assembly: ApiConventionType(typeof(DefaultApiConventions))]
|
||||
[assembly: ApiConventionType(typeof(DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCodeConvention))]
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Analyzers
|
||||
{
|
||||
|
|
@ -22,4 +23,11 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers
|
|||
return Ok();
|
||||
}
|
||||
}
|
||||
|
||||
public static class DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCodeConvention
|
||||
{
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(404)]
|
||||
public static void Get(int id) { }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue