private async Task <ActualApiResponseMetadata?> RunInspectReturnStatementSyntax(string source, string test) { var project = DiagnosticProject.Create(GetType().Assembly, new[] { source }); var compilation = await project.GetCompilationAsync(); var symbolCache = new ApiControllerSymbolCache(compilation); 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(); return(SymbolApiResponseMetadataProvider.InspectReturnStatementSyntax( symbolCache, compilation.GetSemanticModel(syntaxTree), returnStatement, CancellationToken.None)); }
private 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 misisng a true condition or has an else expression, skip this operation. return; } var parent = ifOperation.Parent; if (parent?.Kind == OperationKind.Block) { 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; } var semanticModel = operationAnalysisContext.Compilation.GetSemanticModel(methodSyntax.SyntaxTree); 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 returnStatementSyntax = (ReturnStatementSyntax)returnOperation.Syntax; var actualMetadata = SymbolApiResponseMetadataProvider.InspectReturnStatementSyntax( symbolCache, semanticModel, returnStatementSyntax, operationAnalysisContext.CancellationToken); if (actualMetadata == null || actualMetadata.Value.StatusCode != 400) { return; } var additionalLocations = new[] { ifStatement.GetLocation(), returnStatementSyntax.GetLocation(), }; operationAnalysisContext.ReportDiagnostic( Diagnostic.Create( DiagnosticDescriptors.MVC1007_ApiActionsDoNotRequireExplicitModelValidationCheck, ifStatement.GetLocation(), additionalLocations: additionalLocations)); }, OperationKind.Conditional); }