protected override void InitializeWorker(ApiControllerAnalyzerContext analyzerContext) { analyzerContext.Context.RegisterSymbolAction(context => { var method = (IMethodSymbol)context.Symbol; if (!analyzerContext.IsApiAction(method)) { return; } foreach (var attribute in method.GetAttributes()) { if (attribute.AttributeClass.IsAssignableFrom(analyzerContext.RouteAttribute)) { return; } } var properties = ImmutableDictionary.Create <string, string>(StringComparer.Ordinal) .Add(MethodNameKey, method.Name); var location = method.Locations.Length > 0 ? method.Locations[0] : Location.None; context.ReportDiagnostic(Diagnostic.Create( SupportedDiagnostic, location, properties: properties)); }, SymbolKind.Method); }
protected override void InitializeWorker(ApiControllerAnalyzerContext analyzerContext) { analyzerContext.Context.RegisterSyntaxNodeAction(context => { var methodSyntax = (MethodDeclarationSyntax)context.Node; if (methodSyntax.Body == null) { // Ignore expression bodied methods. return; } var method = context.SemanticModel.GetDeclaredSymbol(methodSyntax, context.CancellationToken); if (!analyzerContext.IsApiAction(method)) { return; } if (method.ReturnsVoid || method.ReturnType == analyzerContext.SystemThreadingTaskOfT) { // Void or Task returning methods. We don't have to check anything here since we're specifically // looking for return BadRequest(..); return; } // Only look for top level statements that look like "if (!ModelState.IsValid)" foreach (var memberAccessSyntax in methodSyntax.Body.DescendantNodes().OfType <MemberAccessExpressionSyntax>()) { var ancestorIfStatement = memberAccessSyntax.FirstAncestorOrSelf <IfStatementSyntax>(); if (ancestorIfStatement == null) { // Node's not in an if statement. continue; } var symbolInfo = context.SemanticModel.GetSymbolInfo(memberAccessSyntax, context.CancellationToken); if (!(symbolInfo.Symbol is IPropertySymbol property) || (property.ContainingType != analyzerContext.ModelStateDictionary) || !string.Equals(property.Name, "IsValid", StringComparison.Ordinal) || !IsFalseExpression(memberAccessSyntax)) { continue; } var containingBlock = (SyntaxNode)ancestorIfStatement; if (containingBlock.Parent.Kind() == SyntaxKind.ElseClause) { containingBlock = containingBlock.Parent; } context.ReportDiagnostic(Diagnostic.Create(SupportedDiagnostic, containingBlock.GetLocation())); return; } }, SyntaxKind.MethodDeclaration); }
public sealed override void Initialize(AnalysisContext context) { context.RegisterCompilationStartAction(compilationContext => { var analyzerContext = new ApiControllerAnalyzerContext(compilationContext); // Only do work if ApiControllerAttribute is defined. if (analyzerContext.ApiControllerAttribute == null) { return; } InitializeWorker(analyzerContext); }); }
protected override void InitializeWorker(ApiControllerAnalyzerContext analyzerContext) { analyzerContext.Context.RegisterSyntaxNodeAction(context => { var methodSyntax = (MethodDeclarationSyntax)context.Node; if (methodSyntax.Body == null) { // Ignore expression bodied methods. } var method = context.SemanticModel.GetDeclaredSymbol(methodSyntax, context.CancellationToken); if (!analyzerContext.IsApiAction(method)) { return; } if (method.ReturnsVoid || method.ReturnType.Kind != SymbolKind.NamedType) { return; } var declaredReturnType = method.ReturnType; var namedReturnType = (INamedTypeSymbol)method.ReturnType; var isTaskOActionResult = false; if (namedReturnType.ConstructedFrom?.IsAssignableFrom(analyzerContext.SystemThreadingTaskOfT) ?? false) { // Unwrap Task<T>. isTaskOActionResult = true; declaredReturnType = namedReturnType.TypeArguments[0]; } if (!declaredReturnType.IsAssignableFrom(analyzerContext.IActionResult)) { // Method signature does not look like IActionResult MyAction or SomeAwaitable<IActionResult>. // Nothing to do here. return; } // Method returns an IActionResult. Determine if the method block returns an ObjectResult foreach (var returnStatement in methodSyntax.DescendantNodes().OfType <ReturnStatementSyntax>()) { var returnType = context.SemanticModel.GetTypeInfo(returnStatement.Expression, context.CancellationToken); if (returnType.Type == null || returnType.Type.Kind == SymbolKind.ErrorType) { continue; } ImmutableDictionary <string, string> properties = null; if (returnType.Type.IsAssignableFrom(analyzerContext.ObjectResult)) { // Check if the method signature looks like "return Ok(userModelInstance)". If so, we can infer the type of userModelInstance if (returnStatement.Expression is InvocationExpressionSyntax invocation && invocation.ArgumentList.Arguments.Count == 1) { var typeInfo = context.SemanticModel.GetTypeInfo(invocation.ArgumentList.Arguments[0].Expression); var desiredReturnType = analyzerContext.ActionResultOfT.Construct(typeInfo.Type); if (isTaskOActionResult) { desiredReturnType = analyzerContext.SystemThreadingTaskOfT.Construct(desiredReturnType); } var desiredReturnTypeString = desiredReturnType.ToMinimalDisplayString( context.SemanticModel, methodSyntax.ReturnType.SpanStart); properties = ImmutableDictionary.Create <string, string>(StringComparer.Ordinal) .Add(ReturnTypeKey, desiredReturnTypeString); } context.ReportDiagnostic(Diagnostic.Create( SupportedDiagnostic, methodSyntax.ReturnType.GetLocation(), properties: properties)); } } }, SyntaxKind.MethodDeclaration); }
protected abstract void InitializeWorker(ApiControllerAnalyzerContext analyzerContext);