Exemple #1
0
    public async Task IsApiControllerAction_ReturnsFalse_IfMethodReturnTypeIsInvalid()
    {
        // Arrange
        var source      = @"
using Microsoft.AspNetCore.Mvc;

namespace TestNamespace
{
    [ApiController]
    public class TestController : ControllerBase
    {
        public DoesNotExist Get(int id)
        {
            if (id == 0)
            {
                return NotFound();
            }

            return new DoesNotExist(id);
        }
    }
}";
        var project     = MvcDiagnosticAnalyzerRunner.CreateProjectWithReferencesInBinDir(GetType().Assembly, new[] { source });
        var compilation = await project.GetCompilationAsync();

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));
        var method = (IMethodSymbol)compilation.GetTypeByMetadataName("TestNamespace.TestController").GetMembers("Get").First();

        // Act
        var result = ApiControllerFacts.IsApiControllerAction(symbolCache, method);

        // Assert
        Assert.False(result);
    }
    private void InitializeWorker(CompilationStartAnalysisContext compilationStartAnalysisContext, ApiControllerSymbolCache symbolCache)
    {
        compilationStartAnalysisContext.RegisterOperationAction(operationStartContext =>
        {
            var method = (IMethodSymbol)operationStartContext.ContainingSymbol;
            if (!ApiControllerFacts.IsApiControllerAction(symbolCache, method))
            {
                return;
            }

            var declaredResponseMetadata = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);
            var hasUnreadableStatusCodes = !ActualApiResponseMetadataFactory.TryGetActualResponseMetadata(symbolCache, (IMethodBodyOperation)operationStartContext.Operation, operationStartContext.CancellationToken, out var actualResponseMetadata);

            var hasUndocumentedStatusCodes = false;
            foreach (var actualMetadata in actualResponseMetadata)
            {
                var location = actualMetadata.ReturnOperation.ReturnedValue.Syntax.GetLocation();

                if (!DeclaredApiResponseMetadata.Contains(declaredResponseMetadata, actualMetadata))
                {
                    hasUndocumentedStatusCodes = true;
                    if (actualMetadata.IsDefaultResponse)
                    {
                        operationStartContext.ReportDiagnostic(Diagnostic.Create(
                                                                   ApiDiagnosticDescriptors.API1001_ActionReturnsUndocumentedSuccessResult,
                                                                   location));
                    }
                    else
                    {
                        operationStartContext.ReportDiagnostic(Diagnostic.Create(
                                                                   ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode,
                                                                   location,
                                                                   actualMetadata.StatusCode));
                    }
                }
            }

            if (hasUndocumentedStatusCodes || hasUnreadableStatusCodes)
            {
                // If we produced analyzer warnings about undocumented status codes, don't attempt to determine
                // if there are documented status codes that are missing from the method body.
                return;
            }

            for (var i = 0; i < declaredResponseMetadata.Count; i++)
            {
                var declaredMetadata = declaredResponseMetadata[i];
                if (!Contains(actualResponseMetadata, declaredMetadata))
                {
                    operationStartContext.ReportDiagnostic(Diagnostic.Create(
                                                               ApiDiagnosticDescriptors.API1002_ActionDoesNotReturnDocumentedStatusCode,
                                                               method.Locations[0],
                                                               declaredMetadata.StatusCode));
                }
            }
        }, OperationKind.MethodBody);
    }
Exemple #3
0
    public async Task IsApiControllerAction_ReturnsTrue_ForValidActionMethods()
    {
        // Arrange
        var compilation = await GetCompilation();

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));
        var type   = compilation.GetTypeByMetadataName(typeof(ApiConventionAnalyzerTest_Valid).FullName);
        var method = (IMethodSymbol)type.GetMembers(nameof(ApiConventionAnalyzerTest_Valid.Index)).First();

        // Act
        var result = ApiControllerFacts.IsApiControllerAction(symbolCache, method);

        // Assert
        Assert.True(result);
    }
Exemple #4
0
    public async Task IsApiControllerAction_ReturnsFalse_IfContainingTypeIsNotController()
    {
        // Arrange
        var compilation = await GetCompilation();

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));
        var type   = compilation.GetTypeByMetadataName(typeof(ApiConventionAnalyzerTest_IndexModel).FullName);
        var method = (IMethodSymbol)type.GetMembers(nameof(ApiConventionAnalyzerTest_IndexModel.OnGet)).First();

        // Act
        var result = ApiControllerFacts.IsApiControllerAction(symbolCache, method);

        // Assert
        Assert.False(result);
    }
Exemple #5
0
    public async Task IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssembly()
    {
        // Arrange
        var compilation = await GetCompilation(nameof(IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssembly));

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));
        var type   = compilation.GetTypeByMetadataName(typeof(IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssemblyController).FullName);
        var method = (IMethodSymbol)type.GetMembers(nameof(IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssemblyController.Action)).First();

        // Act
        var result = ApiControllerFacts.IsApiControllerAction(symbolCache, method);

        // Assert
        Assert.True(result);
    }
    private static void InitializeWorker(CompilationStartAnalysisContext context, ApiControllerSymbolCache symbolCache)
    {
        context.RegisterOperationAction(operationAnalysisContext =>
        {
            var ifOperation = (IConditionalOperation)operationAnalysisContext.Operation;
            if (!(ifOperation.Syntax is IfStatementSyntax ifStatement))
            {
                return;
            }

            if (ifOperation.WhenTrue == null || ifOperation.WhenFalse != null)
            {
                // We only support expressions of the format
                // if (!ModelState.IsValid)
                // or
                // if (ModelState.IsValid == false)
                // If the conditional is missing a true condition or has an else expression, skip this operation.
                return;
            }

            var parent = ifOperation.Parent;
            if (parent == null)
            {
                // No parent, nothing to do
                return;
            }

            if (parent.Kind == OperationKind.Block && parent.Parent != null)
            {
                parent = parent.Parent;
            }

            if (parent.Kind != OperationKind.MethodBodyOperation)
            {
                // Only support top-level ModelState IsValid checks.
                return;
            }

            var trueStatement = UnwrapSingleStatementBlock(ifOperation.WhenTrue);
            if (trueStatement.Kind != OperationKind.Return)
            {
                // We need to verify that the if statement does a ModelState.IsValid check and that the block inside contains
                // a single return statement returning a 400. We'l get to it in just a bit
                return;
            }

            if (!(parent.Syntax is MethodDeclarationSyntax methodSyntax))
            {
                return;
            }

#pragma warning disable RS1030 // Do not invoke Compilation.GetSemanticModel() method within a diagnostic analyzer
            var semanticModel = operationAnalysisContext.Compilation.GetSemanticModel(methodSyntax.SyntaxTree);
#pragma warning restore RS1030 // Do not invoke Compilation.GetSemanticModel() method within a diagnostic analyzer
            var methodSymbol = semanticModel.GetDeclaredSymbol(methodSyntax, operationAnalysisContext.CancellationToken);

            if (!ApiControllerFacts.IsApiControllerAction(symbolCache, methodSymbol))
            {
                // Not a ApiController. Nothing to do here.
                return;
            }

            if (!IsModelStateIsValidCheck(symbolCache, ifOperation.Condition))
            {
                return;
            }

            var returnOperation = (IReturnOperation)trueStatement;

            var returnValue = returnOperation.ReturnedValue;
            if (returnValue == null ||
                !symbolCache.IActionResult.IsAssignableFrom(returnValue.Type))
            {
                return;
            }

            var actualMetadata = ActualApiResponseMetadataFactory.InspectReturnOperation(
                in symbolCache,
                returnOperation);

            if (actualMetadata == null || actualMetadata.Value.StatusCode != 400)
            {
                return;
            }

            var returnStatementSyntax = returnOperation.Syntax;
            var additionalLocations   = new[]
            {
                ifStatement.GetLocation(),
                returnStatementSyntax.GetLocation(),
            };

            operationAnalysisContext.ReportDiagnostic(
                Diagnostic.Create(
                    ApiDiagnosticDescriptors.API1003_ApiActionsDoNotRequireExplicitModelValidationCheck,
                    ifStatement.GetLocation(),
                    additionalLocations: additionalLocations));
        }, OperationKind.Conditional);
    }