Пример #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);
    }
Пример #2
0
    public static bool IsApiControllerAction(ApiControllerSymbolCache symbolCache, IMethodSymbol method)
    {
        if (method == null)
        {
            return(false);
        }

        if (method.ReturnsVoid || method.ReturnType.TypeKind == TypeKind.Error)
        {
            return(false);
        }

        if (!MvcFacts.IsController(method.ContainingType, symbolCache.ControllerAttribute, symbolCache.NonControllerAttribute))
        {
            return(false);
        }

        if (!method.ContainingType.HasAttribute(symbolCache.IApiBehaviorMetadata, inherit: true) &&
            !method.ContainingAssembly.HasAttribute(symbolCache.IApiBehaviorMetadata))
        {
            return(false);
        }

        if (!MvcFacts.IsControllerAction(method, symbolCache.NonActionAttribute, symbolCache.IDisposableDispose))
        {
            return(false);
        }

        return(true);
    }
Пример #3
0
    public async Task GetResponseMetadata_ReturnsValuesFromApiConventionMethodAttribute()
    {
        // Arrange
        var compilation = await GetResponseMetadataCompilation();

        var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}");
        var method     = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.GetResponseMetadata_ReturnsValuesFromApiConventionMethodAttribute)).First();

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

        // Act
        var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);

        // Assert
        Assert.Collection(
            result,
            metadata =>
        {
            Assert.Equal(200, metadata.StatusCode);
            Assert.NotNull(metadata.Attribute);
        },
            metadata =>
        {
            Assert.Equal(404, metadata.StatusCode);
            Assert.NotNull(metadata.Attribute);
        },
            metadata =>
        {
            Assert.True(metadata.IsDefault);
        });
    }
 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;
 }
Пример #5
0
    internal static bool IsMatch(ApiControllerSymbolCache symbolCache, IMethodSymbol method, IMethodSymbol conventionMethod)
    {
        return(MethodMatches() && ParametersMatch());

        bool MethodMatches()
        {
            var methodNameMatchBehavior = GetNameMatchBehavior(symbolCache, conventionMethod);

            if (!IsNameMatch(method.Name, conventionMethod.Name, methodNameMatchBehavior))
            {
                return(false);
            }

            return(true);
        }

        bool ParametersMatch()
        {
            var methodParameters           = method.Parameters;
            var conventionMethodParameters = conventionMethod.Parameters;

            for (var i = 0; i < conventionMethodParameters.Length; i++)
            {
                var conventionParameter = conventionMethodParameters[i];
                if (conventionParameter.IsParams)
                {
                    return(true);
                }

                if (methodParameters.Length <= i)
                {
                    return(false);
                }

                var nameMatchBehavior = GetNameMatchBehavior(symbolCache, conventionParameter);
                var typeMatchBehavior = GetTypeMatchBehavior(symbolCache, conventionParameter);

                if (!IsTypeMatch(methodParameters[i].Type, conventionParameter.Type, typeMatchBehavior) ||
                    !IsNameMatch(methodParameters[i].Name, conventionParameter.Name, nameMatchBehavior))
                {
                    return(false);
                }
            }

            // Ensure convention has at least as many parameters as the method. params convention argument are handled
            // inside the for loop.
            return(methodParameters.Length == conventionMethodParameters.Length);
        }
    }
Пример #6
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);
    }
Пример #7
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);
    }
Пример #8
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);
    }
    public override void Initialize(AnalysisContext context)
    {
        context.EnableConcurrentExecution();
        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);

        context.RegisterCompilationStartAction(compilationStartAnalysisContext =>
        {
            if (!ApiControllerSymbolCache.TryCreate(compilationStartAnalysisContext.Compilation, out var symbolCache))
            {
                // No-op if we can't find types we care about.
                return;
            }

            InitializeWorker(compilationStartAnalysisContext, symbolCache);
        });
    }
    private async Task RunMatchTest(string methodName, string conventionMethodName, bool expected)
    {
        var compilation = await GetCompilationAsync();

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

        var testController   = compilation.GetTypeByMetadataName(TestControllerName);
        var testConvention   = compilation.GetTypeByMetadataName(TestConventionName);
        var method           = (IMethodSymbol)testController.GetMembers(methodName).First();
        var conventionMethod = (IMethodSymbol)testConvention.GetMembers(conventionMethodName).First();

        // Act
        var result = IsMatch(symbolCache, method, conventionMethod);

        // Assert
        Assert.Equal(expected, result);
    }
    public async Task GetNameMatchBehavior_ReturnsValueFromAttributes()
    {
        // Arrange
        var expected    = SymbolApiConventionNameMatchBehavior.Prefix;
        var compilation = await GetCompilationAsync();

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

        var testConvention = compilation.GetTypeByMetadataName(TestConventionName);
        var method         = testConvention.GetMembers(nameof(TestConvention.Get)).First();

        // Act
        var result = GetNameMatchBehavior(symbolCache, method);

        // Assert
        Assert.Equal(expected, result);
    }
Пример #12
0
    public async Task GetResponseMetadata_ReturnsEmptySequence_IfNoAttributesArePresent_ForPostAction()
    {
        // Arrange
        var compilation = await GetResponseMetadataCompilation();

        var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerWithoutConvention)}");
        var method     = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerWithoutConvention.PostPerson)).First();

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

        // Act
        var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);

        // Assert
        Assert.Collection(
            result,
            metadata => Assert.True(metadata.IsImplicit));
    }
Пример #13
0
    public async Task GetResponseMetadata_IgnoresProducesAttribute()
    {
        // Arrange
        var compilation = await GetResponseMetadataCompilation();

        var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}");
        var method     = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithProducesAttribute)).First();

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

        // Act
        var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);

        // Assert
        Assert.Collection(
            result,
            metadata => Assert.True(metadata.IsImplicit));
    }
    public async Task GetTypeMatchBehavior_ReturnsValueFromAttributes()
    {
        // Arrange
        var expected    = SymbolApiConventionTypeMatchBehavior.Any;
        var compilation = await GetCompilationAsync();

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

        var testConvention = compilation.GetTypeByMetadataName(TestConventionName);
        var method         = (IMethodSymbol)testConvention.GetMembers(nameof(TestConvention.MethodWithAnyTypeMatchBehaviorParameter)).First();
        var parameter      = method.Parameters[0];

        // Act
        var result = GetTypeMatchBehavior(symbolCache, parameter);

        // Assert
        Assert.Equal(expected, result);
    }
Пример #15
0
    public async Task GetErrorResponseType_ReturnsTypeDefinedAtAction()
    {
        // Arrange
        var compilation = await GetCompilation(nameof(GetErrorResponseType_ReturnsTypeDefinedAtAction));

        var expected = compilation.GetTypeByMetadataName(typeof(GetErrorResponseType_ReturnsTypeDefinedAtActionModel).FullName);

        var type   = compilation.GetTypeByMetadataName(typeof(GetErrorResponseType_ReturnsTypeDefinedAtActionController).FullName);
        var method = (IMethodSymbol)type.GetMembers("Action").First();

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

        // Act
        var result = SymbolApiResponseMetadataProvider.GetErrorResponseType(symbolCache, method);

        // Assert
        Assert.Same(expected, result);
    }
Пример #16
0
    public async Task GetErrorResponseType_ReturnsProblemDetails_IfNoAttributeIsDiscovered()
    {
        // Arrange
        var compilation = await GetCompilation(nameof(GetErrorResponseType_ReturnsProblemDetails_IfNoAttributeIsDiscovered));

        var expected = compilation.GetTypeByMetadataName(typeof(ProblemDetails).FullName);

        var type   = compilation.GetTypeByMetadataName(typeof(GetErrorResponseType_ReturnsProblemDetails_IfNoAttributeIsDiscoveredController).FullName);
        var method = (IMethodSymbol)type.GetMembers("Action").First();

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

        // Act
        var result = SymbolApiResponseMetadataProvider.GetErrorResponseType(symbolCache, method);

        // Assert
        Assert.Same(expected, result);
    }
Пример #17
0
    private async Task <ActualApiResponseMetadata?> RunInspectReturnStatementSyntax([CallerMemberName] string test = null)
    {
        // Arrange
        var compilation = await GetCompilation("InspectReturnExpressionTests");

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

        var controllerType = compilation.GetTypeByMetadataName(typeof(TestFiles.InspectReturnExpressionTests.TestController).FullName);
        var syntaxTree     = controllerType.DeclaringSyntaxReferences[0].SyntaxTree;

        var method          = (IMethodSymbol)Assert.Single(controllerType.GetMembers(test));
        var methodSyntax    = syntaxTree.GetRoot().FindNode(method.Locations[0].SourceSpan);
        var returnStatement = methodSyntax.DescendantNodes().OfType <ReturnStatementSyntax>().First();
        var returnOperation = (IReturnOperation)compilation.GetSemanticModel(syntaxTree).GetOperation(returnStatement);

        return(ActualApiResponseMetadataFactory.InspectReturnOperation(
                   symbolCache,
                   returnOperation));
    }
Пример #18
0
    private async Task <ActualApiResponseMetadata?> RunInspectReturnStatementSyntax(string source, string test)
    {
        var project     = MvcDiagnosticAnalyzerRunner.CreateProjectWithReferencesInBinDir(GetType().Assembly, new[] { source });
        var compilation = await project.GetCompilationAsync();

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

        var returnType = compilation.GetTypeByMetadataName($"{Namespace}.{test}");
        var syntaxTree = returnType.DeclaringSyntaxReferences[0].SyntaxTree;

        var method          = (IMethodSymbol)returnType.GetMembers().First();
        var methodSyntax    = syntaxTree.GetRoot().FindNode(method.Locations[0].SourceSpan);
        var returnStatement = methodSyntax.DescendantNodes().OfType <ReturnStatementSyntax>().First();
        var returnOperation = (IReturnOperation)compilation.GetSemanticModel(syntaxTree).GetOperation(returnStatement);

        return(ActualApiResponseMetadataFactory.InspectReturnOperation(
                   symbolCache,
                   returnOperation));
    }
Пример #19
0
    private async Task <(bool result, IList <ActualApiResponseMetadata> responseMetadatas, TestSource testSource)> TryGetActualResponseMetadata(string typeName, string methodName)
    {
        var testSource = MvcTestSource.Read(GetType().Name, "TryGetActualResponseMetadataTests");
        var project    = MvcDiagnosticAnalyzerRunner.CreateProjectWithReferencesInBinDir(GetType().Assembly, new[] { testSource.Source });

        var compilation = await GetCompilation("TryGetActualResponseMetadataTests");

        var type   = compilation.GetTypeByMetadataName(typeName);
        var method = (IMethodSymbol)type.GetMembers(methodName).First();

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

        var syntaxTree      = method.DeclaringSyntaxReferences[0].SyntaxTree;
        var methodSyntax    = (MethodDeclarationSyntax)syntaxTree.GetRoot().FindNode(method.Locations[0].SourceSpan);
        var methodOperation = (IMethodBodyBaseOperation)compilation.GetSemanticModel(syntaxTree).GetOperation(methodSyntax);

        var result = ActualApiResponseMetadataFactory.TryGetActualResponseMetadata(symbolCache, methodOperation, CancellationToken.None, out var responseMetadatas);

        return(result, responseMetadatas, testSource);
    }
Пример #20
0
    internal static SymbolApiConventionTypeMatchBehavior GetTypeMatchBehavior(ApiControllerSymbolCache symbolCache, ISymbol symbol)
    {
        var attribute = symbol.GetAttributes(symbolCache.ApiConventionTypeMatchAttribute).FirstOrDefault();

        if (attribute == null ||
            attribute.ConstructorArguments.Length != 1 ||
            attribute.ConstructorArguments[0].Kind != TypedConstantKind.Enum)
        {
            return(SymbolApiConventionTypeMatchBehavior.AssignableFrom);
        }

        var argEnum = attribute.ConstructorArguments[0].Value;

        if (argEnum == null)
        {
            throw new ArgumentNullException(nameof(symbol));
        }

        var intValue = (int)argEnum;

        return((SymbolApiConventionTypeMatchBehavior)intValue);
    }
Пример #21
0
    internal static SymbolApiConventionNameMatchBehavior GetNameMatchBehavior(ApiControllerSymbolCache symbolCache, ISymbol symbol)
    {
        var attribute = symbol.GetAttributes(symbolCache.ApiConventionNameMatchAttribute).FirstOrDefault();

        if (attribute == null ||
            attribute.ConstructorArguments.Length != 1 ||
            attribute.ConstructorArguments[0].Kind != TypedConstantKind.Enum)
        {
            return(SymbolApiConventionNameMatchBehavior.Exact);
        }

        var argEnum = attribute.ConstructorArguments[0].Value;

        if (argEnum == null)
        {
            throw new InvalidOperationException($"{nameof(symbolCache.ApiConventionNameMatchAttribute)} does not appear well formed.");
        }

        var intValue = (int)argEnum;

        return((SymbolApiConventionNameMatchBehavior)intValue);
    }
Пример #22
0
    private async Task GetResponseMetadata_WorksForInvalidOrUnsupportedAttributes(string typeName, string methodName)
    {
        // Arrange
        var compilation = await GetResponseMetadataCompilation();

        var controller = compilation.GetTypeByMetadataName($"{Namespace}.{typeName}");
        var method     = (IMethodSymbol)controller.GetMembers(methodName).First();

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

        // Act
        var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);

        // Assert
        Assert.Collection(
            result,
            metadata =>
        {
            Assert.Equal(200, metadata.StatusCode);
            Assert.Same(method, metadata.AttributeSource);
        });
    }
Пример #23
0
    private async Task <CodeActionContext?> CreateCodeActionContext(CancellationToken cancellationToken)
    {
        var root = await _document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

        var semanticModel = await _document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

        var diagnosticNode = root.FindNode(_diagnostic.Location.SourceSpan);
        var methodSyntax   = diagnosticNode.FirstAncestorOrSelf <MethodDeclarationSyntax>();
        var method         = semanticModel.GetDeclaredSymbol(methodSyntax, cancellationToken);

        var statusCodesType     = semanticModel.Compilation.GetTypeByMetadataName(ApiSymbolNames.HttpStatusCodes);
        var statusCodeConstants = GetStatusCodeConstants(statusCodesType);

        if (!ApiControllerSymbolCache.TryCreate(semanticModel.Compilation, out var symbolCache))
        {
            return(null);
        }

        var codeActionContext = new CodeActionContext(semanticModel, symbolCache, method, methodSyntax, statusCodeConstants, cancellationToken);

        return(codeActionContext);
    }
Пример #24
0
    public async Task GetDeclaredResponseMetadata_ApiConventionTypeAttributeOnBaseType_Works()
    {
        // Arrange
        var type        = typeof(GetDeclaredResponseMetadata_ApiConventionTypeAttributeOnBaseType);
        var compilation = await GetResponseMetadataCompilation();

        var controller = compilation.GetTypeByMetadataName(type.FullName);
        var method     = (IMethodSymbol)controller.GetMembers().First();

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

        // Act
        var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);

        // Assert
        // We should expect 3 entries specified by DefaultApiConventions.Post
        Assert.Collection(
            result.OrderBy(r => r.StatusCode),
            metadata => Assert.True(metadata.IsDefault),
            metadata => Assert.Equal(201, metadata.StatusCode),
            metadata => Assert.Equal(400, metadata.StatusCode));
    }
Пример #25
0
    public async Task GetResponseMetadata_ReturnsValueFromProducesResponseType_WhenStatusCodeIsSpecifiedInConstructorWithResponseType()
    {
        // Arrange
        var compilation = await GetResponseMetadataCompilation();

        var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}");
        var method     = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithProducesResponseType_StatusCodeAndTypeInConstructor)).First();

        Assert.True(ApiControllerSymbolCache.TryCreate(compilation, out var symbolCache));

        // Act
        var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);

        // Assert
        Assert.Collection(
            result,
            metadata =>
        {
            Assert.Equal(202, metadata.StatusCode);
            Assert.NotNull(metadata.Attribute);
            Assert.Equal(method, metadata.AttributeSource);
        });
    }
Пример #26
0
    public async Task InspectReturnExpression_ReturnsNull_IfReturnExpressionCannotBeFound()
    {
        // Arrange & Act
        var source      = @"
            using Microsoft.AspNetCore.Mvc;

namespace Microsoft.AspNetCore.Mvc.Api.Analyzers
{
    [ApiController]
    public class TestController : ControllerBase
    {
        public IActionResult Get(int id)
        {
            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 returnType = compilation.GetTypeByMetadataName($"{Namespace}.TestController");
        var syntaxTree = returnType.DeclaringSyntaxReferences[0].SyntaxTree;

        var method          = (IMethodSymbol)returnType.GetMembers().First();
        var methodSyntax    = syntaxTree.GetRoot().FindNode(method.Locations[0].SourceSpan);
        var returnStatement = methodSyntax.DescendantNodes().OfType <ReturnStatementSyntax>().First();
        var returnOperation = (IReturnOperation)compilation.GetSemanticModel(syntaxTree).GetOperation(returnStatement);

        var actualResponseMetadata = ActualApiResponseMetadataFactory.InspectReturnOperation(
            symbolCache,
            returnOperation);

        // Assert
        Assert.Null(actualResponseMetadata);
    }
Пример #27
0
    public static bool TryCreate(Compilation compilation, out ApiControllerSymbolCache symbolCache)
    {
        symbolCache = default;

        if (!TryGetType(ApiSymbolNames.ApiConventionMethodAttribute, out var apiConventionMethodAttribute))
        {
            return(false);
        }

        if (!TryGetType(ApiSymbolNames.ApiConventionNameMatchAttribute, out var apiConventionNameMatchAttribute))
        {
            return(false);
        }

        if (!TryGetType(ApiSymbolNames.ApiConventionTypeAttribute, out var apiConventionTypeAttribute))
        {
            return(false);
        }

        if (!TryGetType(ApiSymbolNames.ApiConventionTypeMatchAttribute, out var apiConventionTypeMatchAttribute))
        {
            return(false);
        }

        if (!TryGetType(ApiSymbolNames.ControllerAttribute, out var controllerAttribute))
        {
            return(false);
        }

        if (!TryGetType(ApiSymbolNames.DefaultStatusCodeAttribute, out var defaultStatusCodeAttribute))
        {
            return(false);
        }

        if (!TryGetType(ApiSymbolNames.IActionResult, out var iActionResult))
        {
            return(false);
        }

        if (!TryGetType(ApiSymbolNames.IApiBehaviorMetadata, out var iApiBehaviorMetadata))
        {
            return(false);
        }

        if (!TryGetType(ApiSymbolNames.ModelStateDictionary, out var modelStateDictionary))
        {
            return(false);
        }

        if (!TryGetType(ApiSymbolNames.NonActionAttribute, out var nonActionAttribute))
        {
            return(false);
        }

        if (!TryGetType(ApiSymbolNames.NonControllerAttribute, out var nonControllerAttribute))
        {
            return(false);
        }

        if (!TryGetType(ApiSymbolNames.ProblemDetails, out var problemDetails))
        {
            return(false);
        }

        if (!TryGetType(ApiSymbolNames.ProducesDefaultResponseTypeAttribute, out var producesDefaultResponseTypeAttribute))
        {
            return(false);
        }

        if (!TryGetType(ApiSymbolNames.ProducesErrorResponseTypeAttribute, out var producesErrorResponseTypeAttribute))
        {
            return(false);
        }

        if (!TryGetType(ApiSymbolNames.ProducesResponseTypeAttribute, out var producesResponseTypeAttribute))
        {
            return(false);
        }

        var statusCodeActionResult = compilation.GetTypeByMetadataName(ApiSymbolNames.IStatusCodeActionResult);
        var statusCodeActionResultStatusProperty = (IPropertySymbol?)statusCodeActionResult?.GetMembers("StatusCode")[0];

        if (statusCodeActionResultStatusProperty == null)
        {
            return(false);
        }

        var disposable         = compilation.GetSpecialType(SpecialType.System_IDisposable);
        var members            = disposable?.GetMembers(nameof(IDisposable.Dispose));
        var iDisposableDispose = (IMethodSymbol?)members?[0];

        if (iDisposableDispose == null)
        {
            return(false);
        }

        symbolCache = new ApiControllerSymbolCache(
            apiConventionMethodAttribute,
            apiConventionNameMatchAttribute,
            apiConventionTypeAttribute,
            apiConventionTypeMatchAttribute,
            controllerAttribute,
            defaultStatusCodeAttribute,
            iActionResult,
            iApiBehaviorMetadata,
            iDisposableDispose,
            statusCodeActionResultStatusProperty,
            modelStateDictionary,
            nonActionAttribute,
            nonControllerAttribute,
            problemDetails,
            producesDefaultResponseTypeAttribute,
            producesResponseTypeAttribute,
            producesErrorResponseTypeAttribute);

        return(true);

        bool TryGetType(string typeName, out INamedTypeSymbol typeSymbol)
        {
            typeSymbol = compilation.GetTypeByMetadataName(typeName);
            return(typeSymbol != null && typeSymbol.TypeKind != TypeKind.Error);
        }
    }
Пример #28
0
    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);
    }
    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);
    }