| File: ApiConventionAnalyzer.cs | Web Access |
| Project: src\aspnetcore\src\Mvc\Mvc.Api.Analyzers\src\Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj (Microsoft.AspNetCore.Mvc.Api.Analyzers) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; namespace Microsoft.AspNetCore.Mvc.Api.Analyzers; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class ApiConventionAnalyzer : DiagnosticAnalyzer { public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create( ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, ApiDiagnosticDescriptors.API1001_ActionReturnsUndocumentedSuccessResult, ApiDiagnosticDescriptors.API1002_ActionDoesNotReturnDocumentedStatusCode); public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.RegisterCompilationStartAction(context => { if (!ApiControllerSymbolCache.TryCreate(context.Compilation, out var symbolCache)) { // No-op if we can't find types we care about. return; } InitializeWorker(context, symbolCache); }); } private static void InitializeWorker(CompilationStartAnalysisContext context, ApiControllerSymbolCache symbolCache) { context.RegisterOperationAction(context => { var method = (IMethodSymbol)context.ContainingSymbol; if (!ApiControllerFacts.IsApiControllerAction(symbolCache, method)) { return; } var declaredResponseMetadata = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method); var hasUnreadableStatusCodes = !ActualApiResponseMetadataFactory.TryGetActualResponseMetadata(symbolCache, (IMethodBodyOperation)context.Operation, 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) { context.ReportDiagnostic(Diagnostic.Create( ApiDiagnosticDescriptors.API1001_ActionReturnsUndocumentedSuccessResult, location)); } else { context.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)) { context.ReportDiagnostic(Diagnostic.Create( ApiDiagnosticDescriptors.API1002_ActionDoesNotReturnDocumentedStatusCode, method.Locations[0], declaredMetadata.StatusCode)); } } }, OperationKind.MethodBody); } 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; } }