| File: WebApplicationBuilderFixer.cs | Web Access |
| Project: src\aspnetcore\src\Framework\AspNetCoreAnalyzers\src\CodeFixes\Microsoft.AspNetCore.App.CodeFixes.csproj (Microsoft.AspNetCore.App.CodeFixes) |
// 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.Immutable; using System.Composition; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Microsoft.AspNetCore.Analyzers.WebApplicationBuilder.Fixers; [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(WebApplicationBuilderFixer)), Shared] public sealed class WebApplicationBuilderFixer : CodeFixProvider { public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create( // Add other diagnostic descriptor id's DiagnosticDescriptors.DoNotUseHostConfigureLogging.Id, DiagnosticDescriptors.DoNotUseHostConfigureServices.Id, DiagnosticDescriptors.DisallowConfigureAppConfigureHostBuilder.Id ); public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); if (root == null) { return; } foreach (var diagnostic in context.Diagnostics) { var id = diagnostic.Id; var message = string.Empty; var identifierMethod = string.Empty; switch (id) { case string when id == DiagnosticDescriptors.DoNotUseHostConfigureLogging.Id: message = "Fix references to Logging properties on WebApplicationBuilder"; identifierMethod = "Logging"; break; case string when id == DiagnosticDescriptors.DoNotUseHostConfigureServices.Id: message = "Fix references to Services properties on WebApplicationBuilder"; identifierMethod = "Services"; break; case string when id == DiagnosticDescriptors.DisallowConfigureAppConfigureHostBuilder.Id: message = "Fix references to Configuration properties on WebApplicationBuilder"; identifierMethod = "Configuration"; break; } if (!CanFixWebApplicationBuilder(diagnostic, SyntaxFactory.IdentifierName(identifierMethod), root, out var invocation)) { continue; } context.RegisterCodeFix( CodeAction.Create( message, cancellationToken => FixWebApplicationBuilderAsync(diagnostic, root, context.Document, invocation), equivalenceKey: id), diagnostic); } } private static Task<Document> FixWebApplicationBuilderAsync(Diagnostic diagnostic, SyntaxNode root, Document document, InvocationExpressionSyntax invocation) { var diagnosticTarget = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); return Task.FromResult(document.WithSyntaxRoot(root.ReplaceNode(diagnosticTarget, invocation))); } private static bool CanFixWebApplicationBuilder(Diagnostic diagnostic, IdentifierNameSyntax identifierMethod, SyntaxNode root, [NotNullWhen(true)] out InvocationExpressionSyntax? invocationName) { invocationName = null; if (root == null) { return false; } // builder.Host.ConfigureLogging(builder => builder.AddJsonConsole()); var diagnosticTarget = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); if (diagnosticTarget is InvocationExpressionSyntax invocation) { // No modification are made if the invocation isn't accessing a method on `builder.Host` or `builder.WebHost`. if (invocation.Expression is not MemberAccessExpressionSyntax hostBasedInvocationMethodExpr || hostBasedInvocationMethodExpr.Expression is not MemberAccessExpressionSyntax configureMethodOnHostAccessExpr) { return false; } configureMethodOnHostAccessExpr = configureMethodOnHostAccessExpr.WithName(identifierMethod); var indentation = hostBasedInvocationMethodExpr.GetLeadingTrivia(); // builder.Host.ConfigureLogging => builder.Logging // builder.WebHost.ConfigureServices => builder.Services hostBasedInvocationMethodExpr = hostBasedInvocationMethodExpr.WithExpression(configureMethodOnHostAccessExpr) .NormalizeWhitespace().WithLeadingTrivia(indentation); if (invocation.ArgumentList.Arguments.SingleOrDefault() is not { } initArgument || initArgument.Expression is not LambdaExpressionSyntax lambdaExpr) { return false; } if (lambdaExpr.Block != null) { var lambdaStatements = lambdaExpr.Block.Statements; foreach (var statement in lambdaStatements) { if (statement is not ExpressionStatementSyntax currentStatement || currentStatement.Expression is not InvocationExpressionSyntax expr) { return false; } // arguments of builder.{method_name}({arguments}) var argument = expr.ArgumentList; if (expr.Expression is not MemberAccessExpressionSyntax bodyExpression) //builder.{method_name} { return false; } var method = bodyExpression.Name; // method_name hostBasedInvocationMethodExpr = hostBasedInvocationMethodExpr.WithName(method); invocation = invocation.Update(hostBasedInvocationMethodExpr, argument); hostBasedInvocationMethodExpr = hostBasedInvocationMethodExpr.WithExpression(invocation); } } else { if (lambdaExpr.ExpressionBody is not InvocationExpressionSyntax body) { return false; } var arguments = body.ArgumentList; if (body.Expression is not MemberAccessExpressionSyntax bodyExpression) { return false; } var method = bodyExpression.Name; hostBasedInvocationMethodExpr = hostBasedInvocationMethodExpr.WithName(method); invocation = invocation.WithExpression(hostBasedInvocationMethodExpr).WithArgumentList(arguments); } invocationName = invocation; return true; } return false; } }