예제 #1
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);
        }
        public void Matches_ReturnsTrue_IfDeclaredMetadataAndActualMetadataHaveSameStatusCode()
        {
            // Arrange
            var declaredMetadata = DeclaredApiResponseMetadata.ForProducesResponseType(302, AttributeData, Mock.Of <IMethodSymbol>());
            var actualMetadata   = new ActualApiResponseMetadata(ReturnStatement, 302, null);

            // Act
            var matches = declaredMetadata.Matches(actualMetadata);

            // Assert
            Assert.True(matches);
        }
        public void Matches_ReturnsFalse_IfDeclaredMetadataIs201_AndActualMetadataIs200()
        {
            // Arrange
            var declaredMetadata = DeclaredApiResponseMetadata.ForProducesResponseType(201, AttributeData, Mock.Of <IMethodSymbol>());
            var actualMetadata   = new ActualApiResponseMetadata(ReturnStatement, 200, null);

            // Act
            var matches = declaredMetadata.Matches(actualMetadata);

            // Assert
            Assert.False(matches);
        }
        public void Matches_ReturnsFalse_IfDeclaredMetadataIsDefault_AndActualMetadataIsNotErrorStatusCode()
        {
            // Arrange
            var declaredMetadata = DeclaredApiResponseMetadata.ForProducesDefaultResponse(AttributeData, Mock.Of <IMethodSymbol>());
            var actualMetadata   = new ActualApiResponseMetadata(ReturnStatement, 204, null);

            // Act
            var matches = declaredMetadata.Matches(actualMetadata);

            // Assert
            Assert.False(matches);
        }
예제 #5
0
        public void Matches_ReturnsTrue_IfDeclaredMetadataIs201_AndActualMetadataIsDefault()
        {
            // Arrange
            var declaredMetadata = DeclaredApiResponseMetadata.ForProducesResponseType(201, AttributeData, Mock.Of <IMethodSymbol>());
            var actualMetadata   = new ActualApiResponseMetadata(ReturnExpression, null);

            // Act
            var matches = declaredMetadata.Matches(actualMetadata);

            // Assert
            Assert.True(matches);
        }
예제 #6
0
        public void Matches_ReturnsTrue_IfDeclaredMetadataIsDefault_AndActualMetadataIsErrorStatusCode(int actualStatusCode)
        {
            // Arrange
            var declaredMetadata = DeclaredApiResponseMetadata.ForProducesDefaultResponse(AttributeData, Mock.Of <IMethodSymbol>());
            var actualMetadata   = new ActualApiResponseMetadata(ReturnExpression, actualStatusCode, null);

            // Act
            var matches = declaredMetadata.Matches(actualMetadata);

            // Assert
            Assert.True(matches);
        }
        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);
        }
        private static IList <DeclaredApiResponseMetadata> GetResponseMetadataFromMethodAttributes(ApiControllerSymbolCache symbolCache, IMethodSymbol methodSymbol)
        {
            var metadataItems = new List <DeclaredApiResponseMetadata>();
            var responseMetadataAttributes = methodSymbol.GetAttributes(symbolCache.ProducesResponseTypeAttribute, inherit: true);

            foreach (var attribute in responseMetadataAttributes)
            {
                var statusCode = GetStatusCode(attribute);
                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);
        }
예제 #9
0
        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);
        }
예제 #10
0
        internal static bool Contains(IList <ActualApiResponseMetadata> actualResponseMetadata, DeclaredApiResponseMetadata declaredMetadata)
        {
            for (var i = 0; i < actualResponseMetadata.Count; i++)
            {
                if (declaredMetadata.Matches(actualResponseMetadata[i]))
                {
                    return(true);
                }
            }

            return(false);
        }
        private void InitializeWorker(CompilationStartAnalysisContext compilationStartAnalysisContext, ApiControllerSymbolCache symbolCache)
        {
            compilationStartAnalysisContext.RegisterSyntaxNodeAction(syntaxNodeContext =>
            {
                var cancellationToken = syntaxNodeContext.CancellationToken;
                var methodSyntax      = (MethodDeclarationSyntax)syntaxNodeContext.Node;
                var semanticModel     = syntaxNodeContext.SemanticModel;
                var method            = semanticModel.GetDeclaredSymbol(methodSyntax, syntaxNodeContext.CancellationToken);

                if (!ApiControllerFacts.IsApiControllerAction(symbolCache, method))
                {
                    return;
                }

                var declaredResponseMetadata = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method);
                var hasUnreadableStatusCodes = !ActualApiResponseMetadataFactory.TryGetActualResponseMetadata(symbolCache, semanticModel, methodSyntax, cancellationToken, out var actualResponseMetadata);

                var hasUndocumentedStatusCodes = false;
                foreach (var actualMetadata in actualResponseMetadata)
                {
                    var location = actualMetadata.ReturnStatement.GetLocation();

                    if (!DeclaredApiResponseMetadata.Contains(declaredResponseMetadata, actualMetadata))
                    {
                        hasUndocumentedStatusCodes = true;
                        if (actualMetadata.IsDefaultResponse)
                        {
                            syntaxNodeContext.ReportDiagnostic(Diagnostic.Create(
                                                                   ApiDiagnosticDescriptors.API1001_ActionReturnsUndocumentedSuccessResult,
                                                                   location));
                        }
                        else
                        {
                            syntaxNodeContext.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))
                    {
                        syntaxNodeContext.ReportDiagnostic(Diagnostic.Create(
                                                               ApiDiagnosticDescriptors.API1002_ActionDoesNotReturnDocumentedStatusCode,
                                                               methodSyntax.Identifier.GetLocation(),
                                                               declaredMetadata.StatusCode));
                    }
                }
            }, SyntaxKind.MethodDeclaration);
        }