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); }
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); }
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; }
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); } }
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); }
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); }
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); }
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)); }
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); }
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); }
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); }
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)); }
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)); }
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); }
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); }
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); }
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); }); }
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); }
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)); }
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); }); }
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); }
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); } }
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); }