From f4f4835635a812cfe24b724b8a1c4aab6cee8705 Mon Sep 17 00:00:00 2001 From: pedro-camargo-MSFT <106186580+pedro-camargo-MSFT@users.noreply.github.com> Date: Tue, 28 Jun 2022 16:23:16 -0700 Subject: [PATCH] Implement analyzer to favor using builder.Logging over ConfigureLogging (#42354) * Add analyzer issue #35816 * Deleting uneccessary using statement and comment * Make DiagnosticDescriptor title and description consistent with other DiagnosticDescriptors * Update src/Framework/AspNetCoreAnalyzers/src/Analyzers/DiagnosticDescriptors.cs Co-authored-by: Safia Abdalla <safia@safia.rocks> Co-authored-by: Safia Abdalla <safia@safia.rocks> --- .../src/Analyzers/DiagnosticDescriptors.cs | 9 + .../WebApplicationBuilderAnalyzer.cs | 40 ++- .../WebApplicationBuilder/WellKnownTypes.cs | 8 + .../DisallowConfigureHostLoggingTest.cs | 278 ++++++++++++++++++ 4 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 src/Framework/AspNetCoreAnalyzers/test/WebApplicationBuilder/DisallowConfigureHostLoggingTest.cs diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/DiagnosticDescriptors.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/DiagnosticDescriptors.cs index c01db137dea..a2d5903de4e 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/DiagnosticDescriptors.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/DiagnosticDescriptors.cs @@ -79,4 +79,13 @@ internal static class DiagnosticDescriptors DiagnosticSeverity.Error, isEnabledByDefault: true, helpLinkUri: "https://aka.ms/aspnet/analyzers"); + + internal static readonly DiagnosticDescriptor DoNotUseHostConfigureLogging = new( + "ASP0011", + "Suggest using builder.Logging over Host.ConfigureLogging or WebHost.ConfigureLogging", + "Suggest using builder.Logging instead of {0}", + "Usage", + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + helpLinkUri: "https://aka.ms/aspnet/analyzers"); } diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/WebApplicationBuilder/WebApplicationBuilderAnalyzer.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/WebApplicationBuilder/WebApplicationBuilderAnalyzer.cs index 01cfdc829a7..0e1b4f220e4 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/WebApplicationBuilder/WebApplicationBuilderAnalyzer.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/WebApplicationBuilder/WebApplicationBuilderAnalyzer.cs @@ -21,6 +21,7 @@ public class WebApplicationBuilderAnalyzer : DiagnosticAnalyzer DiagnosticDescriptors.DoNotUseConfigureWebHostWithConfigureHostBuilder, DiagnosticDescriptors.DoNotUseConfigureWithConfigureWebHostBuilder, DiagnosticDescriptors.DoNotUseUseStartupWithConfigureWebHostBuilder, + DiagnosticDescriptors.DoNotUseHostConfigureLogging }); public override void Initialize(AnalysisContext context) @@ -44,6 +45,11 @@ public class WebApplicationBuilderAnalyzer : DiagnosticAnalyzer wellKnownTypes.HostingAbstractionsWebHostBuilderExtensions, wellKnownTypes.WebHostBuilderExtensions, }; + INamedTypeSymbol[] configureLoggingTypes = + { + wellKnownTypes.HostingHostBuilderExtensions, + wellKnownTypes.WebHostBuilderExtensions + }; compilationStartAnalysisContext.RegisterOperationAction(operationAnalysisContext => { @@ -98,6 +104,38 @@ public class WebApplicationBuilderAnalyzer : DiagnosticAnalyzer invocation)); } + //var builder = WebApplication.CreateBuilder(args); + //builder.Host.ConfigureLogging(x => {}) + if (IsDisallowedMethod( + operationAnalysisContext, + invocation, + targetMethod, + wellKnownTypes.ConfigureHostBuilder, + "ConfigureLogging", + configureLoggingTypes)) + { + operationAnalysisContext.ReportDiagnostic( + CreateDiagnostic( + DiagnosticDescriptors.DoNotUseHostConfigureLogging, + invocation)); + } + + //var builder = WebApplication.CreateBuilder(args); + //builder.WebHost.ConfigureLogging(x => {}) + if (IsDisallowedMethod( + operationAnalysisContext, + invocation, + targetMethod, + wellKnownTypes.ConfigureWebHostBuilder, + "ConfigureLogging", + configureLoggingTypes)) + { + operationAnalysisContext.ReportDiagnostic( + CreateDiagnostic( + DiagnosticDescriptors.DoNotUseHostConfigureLogging, + invocation)); + } + static Diagnostic CreateDiagnostic(DiagnosticDescriptor descriptor, IInvocationOperation operation) { // Take the location for the whole invocation operation as a starting point. @@ -147,7 +185,7 @@ public class WebApplicationBuilderAnalyzer : DiagnosticAnalyzer location = Location.Create(operation.Syntax.SyntaxTree, targetSpan); } - return Diagnostic.Create(descriptor, location); + return Diagnostic.Create(descriptor, location, methodName); } }, OperationKind.Invocation); diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/WebApplicationBuilder/WellKnownTypes.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/WebApplicationBuilder/WellKnownTypes.cs index d4c43aa2a84..0e2f1102d4d 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/WebApplicationBuilder/WellKnownTypes.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/WebApplicationBuilder/WellKnownTypes.cs @@ -42,6 +42,12 @@ internal sealed class WellKnownTypes return false; } + const string HostingHostBuilderExtensions = "Microsoft.Extensions.Hosting.HostingHostBuilderExtensions"; + if (compilation.GetTypeByMetadataName(HostingHostBuilderExtensions) is not { } hostingHostBuilderExtensions) + { + return false; + } + wellKnownTypes = new WellKnownTypes { ConfigureHostBuilder = configureHostBuilder, @@ -49,6 +55,7 @@ internal sealed class WellKnownTypes GenericHostWebHostBuilderExtensions = genericHostWebHostBuilderExtensions, HostingAbstractionsWebHostBuilderExtensions = hostingAbstractionsWebHostBuilderExtensions, WebHostBuilderExtensions = webHostBuilderExtensions, + HostingHostBuilderExtensions = hostingHostBuilderExtensions }; return true; @@ -59,4 +66,5 @@ internal sealed class WellKnownTypes public INamedTypeSymbol GenericHostWebHostBuilderExtensions { get; private init; } public INamedTypeSymbol HostingAbstractionsWebHostBuilderExtensions { get; private init; } public INamedTypeSymbol WebHostBuilderExtensions { get; private init; } + public INamedTypeSymbol HostingHostBuilderExtensions { get; private init; } } diff --git a/src/Framework/AspNetCoreAnalyzers/test/WebApplicationBuilder/DisallowConfigureHostLoggingTest.cs b/src/Framework/AspNetCoreAnalyzers/test/WebApplicationBuilder/DisallowConfigureHostLoggingTest.cs new file mode 100644 index 00000000000..38934ee8d16 --- /dev/null +++ b/src/Framework/AspNetCoreAnalyzers/test/WebApplicationBuilder/DisallowConfigureHostLoggingTest.cs @@ -0,0 +1,278 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System.Globalization; +using Microsoft.AspNetCore.Analyzer.Testing; +using Microsoft.CodeAnalysis; + +namespace Microsoft.AspNetCore.Analyzers.WebApplicationBuilder; +public partial class DisallowConfigureHostLoggingTest +{ + private TestDiagnosticAnalyzerRunner Runner { get; } = new(new WebApplicationBuilderAnalyzer()); + + [Fact] + public async Task DoesNotWarnWhenBuilderLoggingIsUsed() + { + //arrange + var source = @" +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +var builder = WebApplication.CreateBuilder(args); +builder.Logging.AddJsonConsole(); +"; + //act + var diagnostics = await Runner.GetDiagnosticsAsync(source); + //assert + Assert.Empty(diagnostics); + } + + [Fact] + public async Task DoesNotWarnWhenBuilderLoggingIsUsed_InMain() + { + //arrange + var source = @" +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +public static class Program +{ + public static void Main (string[] args) + { + var builder = WebApplication.CreateBuilder(args); + builder.Logging.AddJsonConsole(); + } +} +public class Startup { } +"; + //act + var diagnostics = await Runner.GetDiagnosticsAsync(source); + //assert + Assert.Empty(diagnostics); + } + + [Fact] + public async Task WarnsWhenBuilderLoggingIsNotUsed_Host() + { + //arrange + var source = TestSource.Read(@" +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +var builder = WebApplication.CreateBuilder(args); +builder.Host./*MM*/ConfigureLogging(logging => +{ +logging.AddJsonConsole(); +}); +"); + //act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + //assert + var diagnostic = Assert.Single(diagnostics); + Assert.Same(DiagnosticDescriptors.DoNotUseHostConfigureLogging, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); + Assert.Equal("Suggest using builder.Logging instead of ConfigureLogging", diagnostic.GetMessage(CultureInfo.InvariantCulture)); + } + + [Fact] + public async Task WarnsWhenBuilderLoggingIsNotUsed_WebHost() + { + //arrange + var source = TestSource.Read(@" +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Hosting; +var builder = WebApplication.CreateBuilder(args); +builder.WebHost./*MM*/ConfigureLogging(logging => +{ +logging.AddJsonConsole(); +}); +"); + //act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + //assert + var diagnostic = Assert.Single(diagnostics); + Assert.Same(DiagnosticDescriptors.DoNotUseHostConfigureLogging, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); + Assert.Equal("Suggest using builder.Logging instead of ConfigureLogging", diagnostic.GetMessage(CultureInfo.InvariantCulture)); + } + + [Fact] + public async Task WarnsWhenBuilderLoggingIsNotUsed_OnDifferentLine_Host() + { + //arrange + var source = TestSource.Read(@" +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +var builder = WebApplication.CreateBuilder(args); +builder.Host. + /*MM*/ConfigureLogging(logging => +{ + logging.AddJsonConsole(); +}); +"); + //act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + //assert + var diagnostic = Assert.Single(diagnostics); + Assert.Same(DiagnosticDescriptors.DoNotUseHostConfigureLogging, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); + Assert.Equal("Suggest using builder.Logging instead of ConfigureLogging", diagnostic.GetMessage(CultureInfo.InvariantCulture)); + } + + [Fact] + public async Task WarnsWhenBuilderLoggingIsNotUsed_OnDifferentLine_WebHost() + { + //arrange + var source = TestSource.Read(@" +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Hosting; +var builder = WebApplication.CreateBuilder(args); +builder.WebHost. + /*MM*/ConfigureLogging(logging => +{ +logging.AddJsonConsole(); +}); +"); + //act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + //assert + var diagnostic = Assert.Single(diagnostics); + Assert.Same(DiagnosticDescriptors.DoNotUseHostConfigureLogging, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); + Assert.Equal("Suggest using builder.Logging instead of ConfigureLogging", diagnostic.GetMessage(CultureInfo.InvariantCulture)); + } + + [Fact] + public async Task WarnsWhenBuilderLoggingIsNotUsed_InMain_Host() + { + //arrange + var source = TestSource.Read(@" +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +public static class Program +{ + public static void Main (string[] args) + { + var builder = WebApplication.CreateBuilder(args); + builder.Host./*MM*/ConfigureLogging(logging => + { + logging.AddJsonConsole(); + }); + } +} +public class Startup { } +"); + //act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + //assert + var diagnostic = Assert.Single(diagnostics); + Assert.Same(DiagnosticDescriptors.DoNotUseHostConfigureLogging, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); + Assert.Equal("Suggest using builder.Logging instead of ConfigureLogging", diagnostic.GetMessage(CultureInfo.InvariantCulture)); + } + + [Fact] + public async Task WarnsWhenBuilderLoggingIsNotUsed_InMain_WebHost() + { + //arrange + var source = TestSource.Read(@" +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Hosting; +public static class Program +{ + public static void Main (string[] args) + { + var builder = WebApplication.CreateBuilder(args); + builder.WebHost./*MM*/ConfigureLogging(logging => + { + logging.AddJsonConsole(); + }); + } +} +public class Startup { } +"); + //act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + //assert + var diagnostic = Assert.Single(diagnostics); + Assert.Same(DiagnosticDescriptors.DoNotUseHostConfigureLogging, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); + Assert.Equal("Suggest using builder.Logging instead of ConfigureLogging", diagnostic.GetMessage(CultureInfo.InvariantCulture)); + } + + [Fact] + public async Task WarnsWhenBuilderLoggingIsNotUsed_WhenChained_WebHost() + { + //arrange + var source = TestSource.Read(@" +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Hosting; +var builder = WebApplication.CreateBuilder(args); +builder.WebHost. + /*MM*/ConfigureLogging(logging => { }) + .ConfigureServices(services => { }); +"); + //act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + //assert + var diagnostic = Assert.Single(diagnostics); + Assert.Same(DiagnosticDescriptors.DoNotUseHostConfigureLogging, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); + Assert.Equal("Suggest using builder.Logging instead of ConfigureLogging", diagnostic.GetMessage(CultureInfo.InvariantCulture)); + } + + [Fact] + public async Task WarnsTwiceWhenBuilderLoggingIsNotUsed_Host() + { + //arrange + var source = TestSource.Read(@" +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +var builder = WebApplication.CreateBuilder(args); +builder.Host./*MM1*/ConfigureLogging(logging => +{ +logging.AddJsonConsole(); +}); +builder.Host./*MM2*/ConfigureLogging(logging => +{ +logging.AddJsonConsole(); +}); +"); + //act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + //assert + Assert.Equal(2, diagnostics.Length); + var diagnostic1 = diagnostics[0]; + var diagnostic2 = diagnostics[1]; + + Assert.Same(DiagnosticDescriptors.DoNotUseHostConfigureLogging, diagnostic1.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MM1"], diagnostic1.Location); + Assert.Equal("Suggest using builder.Logging instead of ConfigureLogging", diagnostic1.GetMessage(CultureInfo.InvariantCulture)); + + Assert.Same(DiagnosticDescriptors.DoNotUseHostConfigureLogging, diagnostic2.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MM2"], diagnostic2.Location); + Assert.Equal("Suggest using builder.Logging instead of ConfigureLogging", diagnostic2.GetMessage(CultureInfo.InvariantCulture)); + } + +} + -- GitLab