diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/DiagnosticDescriptors.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/DiagnosticDescriptors.cs index 12505fe54744..08f0860ad7be 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/DiagnosticDescriptors.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/DiagnosticDescriptors.cs @@ -89,6 +89,15 @@ internal static class DiagnosticDescriptors isEnabledByDefault: true, helpLinkUri: "https://aka.ms/aspnet/analyzers"); + internal static readonly DiagnosticDescriptor DoNotUseHostConfigureServices = new( + "ASP0012", + "Suggest using builder.Services over Host.ConfigureServices or WebHost.ConfigureServices", + "Suggest using builder.Services instead of {0}", + "Usage", + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + helpLinkUri: "https://aka.ms/aspnet/analyzers"); + internal static readonly DiagnosticDescriptor DisallowConfigureAppConfigureHostBuilder = new( "ASP0013", "Suggest using WebApplicationBuilder.Configuration over Configure methods", diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/WebApplicationBuilder/WebApplicationBuilderAnalyzer.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/WebApplicationBuilder/WebApplicationBuilderAnalyzer.cs index a538dbc22ae6..e920d5841d2c 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/WebApplicationBuilder/WebApplicationBuilderAnalyzer.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/WebApplicationBuilder/WebApplicationBuilderAnalyzer.cs @@ -22,6 +22,7 @@ public class WebApplicationBuilderAnalyzer : DiagnosticAnalyzer DiagnosticDescriptors.DoNotUseConfigureWithConfigureWebHostBuilder, DiagnosticDescriptors.DoNotUseUseStartupWithConfigureWebHostBuilder, DiagnosticDescriptors.DoNotUseHostConfigureLogging, + DiagnosticDescriptors.DoNotUseHostConfigureServices, DiagnosticDescriptors.DisallowConfigureAppConfigureHostBuilder }); @@ -51,6 +52,11 @@ public override void Initialize(AnalysisContext context) wellKnownTypes.HostingHostBuilderExtensions, wellKnownTypes.WebHostBuilderExtensions }; + INamedTypeSymbol[] configureServicesTypes = + { + wellKnownTypes.HostingHostBuilderExtensions, + wellKnownTypes.ConfigureWebHostBuilder + }; INamedTypeSymbol[] configureAppTypes = { wellKnownTypes.ConfigureHostBuilder, @@ -145,6 +151,38 @@ public override void Initialize(AnalysisContext context) invocation)); } + // var builder = WebApplication.CreateBuilder(args); + // builder.Host.ConfigureServices(x => {}); + if (IsDisallowedMethod( + operationAnalysisContext, + invocation, + targetMethod, + wellKnownTypes.ConfigureHostBuilder, + "ConfigureServices", + configureServicesTypes)) + { + operationAnalysisContext.ReportDiagnostic( + CreateDiagnostic( + DiagnosticDescriptors.DoNotUseHostConfigureServices, + invocation)); + } + + // var builder = WebApplication.CreateBuilder(args); + // builder.WebHost.ConfigureServices(x => {}); + if (IsDisallowedMethod( + operationAnalysisContext, + invocation, + targetMethod, + wellKnownTypes.ConfigureWebHostBuilder, + "ConfigureServices", + configureServicesTypes)) + { + operationAnalysisContext.ReportDiagnostic( + CreateDiagnostic( + DiagnosticDescriptors.DoNotUseHostConfigureServices, + invocation)); + } + // var builder = WebApplication.CreateBuilder(); // builder.WebHost.ConfigureAppConfiguration(builder => {}); if (IsDisallowedMethod( diff --git a/src/Framework/AspNetCoreAnalyzers/test/WebApplicationBuilder/DisallowConfigureServicesTest.cs b/src/Framework/AspNetCoreAnalyzers/test/WebApplicationBuilder/DisallowConfigureServicesTest.cs new file mode 100644 index 000000000000..c8263a9dd89c --- /dev/null +++ b/src/Framework/AspNetCoreAnalyzers/test/WebApplicationBuilder/DisallowConfigureServicesTest.cs @@ -0,0 +1,412 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Analyzer.Testing; + +namespace Microsoft.AspNetCore.Analyzers.WebApplicationBuilder; +public partial class DisallowConfigureServicesTest +{ + private TestDiagnosticAnalyzerRunner Runner { get; } = new(new WebApplicationBuilderAnalyzer()); + + [Fact] + public async Task DoesNotWarnWhenBuilderConfigureServicesIsNotUsed() + { + // Arrange + var source = @" +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddAntiforgery(); +"; + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source); + + // Assert + Assert.Empty(diagnostics); + } + + [Fact] + public async Task WarnsWhenBuilderHostConfigureServicesIsUsed() + { + // Arrange + var source = TestSource.Read(@" +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +var builder = WebApplication.CreateBuilder(args); +builder.Host./*MM*/ConfigureServices(services => +{ +services.AddAntiforgery(); +}); +"); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var diagnostic = Assert.Single(diagnostics); + Assert.Same(DiagnosticDescriptors.DoNotUseHostConfigureServices, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); + Assert.Equal("Suggest using builder.Services instead of ConfigureServices", diagnostic.GetMessage(CultureInfo.InvariantCulture)); + } + + [Fact] + public async Task WarnsWhenBuilderWebHostConfigureServicesIsUsed() + { + // Arrange + var source = TestSource.Read(@" +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +var builder = WebApplication.CreateBuilder(args); +builder.WebHost./*MM*/ConfigureServices(services => +{ +services.AddAntiforgery(); +}); +"); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var diagnostic = Assert.Single(diagnostics); + Assert.Same(DiagnosticDescriptors.DoNotUseHostConfigureServices, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); + Assert.Equal("Suggest using builder.Services instead of ConfigureServices", diagnostic.GetMessage(CultureInfo.InvariantCulture)); + } + + [Fact] + public async Task WarnsWhenBuilderHostConfigureServicesIsUsed_OnDifferentLine() + { + // Arrange + var source = TestSource.Read(@" +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +var builder = WebApplication.CreateBuilder(args); +builder.Host. + /*MM*/ConfigureServices(services => + { + services.AddAntiforgery(); + }); +"); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var diagnostic = Assert.Single(diagnostics); + Assert.Same(DiagnosticDescriptors.DoNotUseHostConfigureServices, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); + Assert.Equal("Suggest using builder.Services instead of ConfigureServices", diagnostic.GetMessage(CultureInfo.InvariantCulture)); + } + + [Fact] + public async Task WarnsWhenBuilderWebHostConfigureServicesIsUsed_OnDifferentLine() + { + // Arrange + var source = TestSource.Read(@" +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +var builder = WebApplication.CreateBuilder(args); +builder.WebHost. + /*MM*/ConfigureServices(services => + { + services.AddAntiforgery(); + }); +"); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var diagnostic = Assert.Single(diagnostics); + Assert.Same(DiagnosticDescriptors.DoNotUseHostConfigureServices, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); + Assert.Equal("Suggest using builder.Services instead of ConfigureServices", diagnostic.GetMessage(CultureInfo.InvariantCulture)); + } + + [Fact] + public async Task DoesNotWarnWhenBuilderConfigureServicesIsNotUsed_InProgramMain() + { + // Arrange + var source = @" +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +public static class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + builder.Services.AddAntiforgery(); + } +} +public class Startup { } +"; + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source); + + // Assert + Assert.Empty(diagnostics); + } + + [Fact] + public async Task WarnsWhenBuilderHostConfigureServicesIsUsedOnProperty_In_Program_Main() + { + // Arrange + var source = TestSource.Read(@" +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +public static class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + builder.Host./*MM*/ConfigureServices(services => + { + services.AddAntiforgery(); + }); + } +} +public class Startup { } +"); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var diagnostic = Assert.Single(diagnostics); + Assert.Same(DiagnosticDescriptors.DoNotUseHostConfigureServices, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); + Assert.Equal("Suggest using builder.Services instead of ConfigureServices", diagnostic.GetMessage(CultureInfo.InvariantCulture)); + } + + [Fact] + public async Task WarnsWhenBuilderWebHostConfigureServicesIsUsedOnProperty_In_Program_Main() + { + // Arrange + var source = TestSource.Read(@" +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +public static class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + builder.WebHost./*MM*/ConfigureServices(services => + { + services.AddAntiforgery(); + }); + } +} +public class Startup { } +"); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var diagnostic = Assert.Single(diagnostics); + Assert.Same(DiagnosticDescriptors.DoNotUseHostConfigureServices, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); + Assert.Equal("Suggest using builder.Services instead of ConfigureServices", diagnostic.GetMessage(CultureInfo.InvariantCulture)); + } + + [Fact] + public async Task WarnsWhenBuilderHostConfigureServicesIsUsed_In_Program_Main() + { + // Arrange + var source = TestSource.Read(@" +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +public static class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + var host = builder.Host; + host./*MM*/ConfigureServices(services => + { + services.AddAntiforgery(); + }); + } +} +public class Startup { } +"); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var diagnostic = Assert.Single(diagnostics); + Assert.Same(DiagnosticDescriptors.DoNotUseHostConfigureServices, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); + Assert.Equal("Suggest using builder.Services instead of ConfigureServices", diagnostic.GetMessage(CultureInfo.InvariantCulture)); + } + + [Fact] + public async Task WarnsWhenBuilderWebHostConfigureServicesIsUsed_In_Program_Main() + { + // Arrange + var source = TestSource.Read(@" +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +public static class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + var webHost = builder.WebHost; + webHost./*MM*/ConfigureServices(services => + { + services.AddAntiforgery(); + }); + } +} +public class Startup { } +"); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var diagnostic = Assert.Single(diagnostics); + Assert.Same(DiagnosticDescriptors.DoNotUseHostConfigureServices, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); + Assert.Equal("Suggest using builder.Services instead of ConfigureServices", diagnostic.GetMessage(CultureInfo.InvariantCulture)); + } + + [Fact] + public async Task WarnsWhenBuilderHostConfigureServicesIsUsed_Inside_Another_Method() + { + // Arrange + var source = TestSource.Read(@" +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +public static class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + ConfigureHost(builder.Host); + } + + private static void ConfigureHost(ConfigureHostBuilder hostBuilder) + { + hostBuilder + ./*MM*/ConfigureServices(services => + { + services.AddAntiforgery(); + }); + } +} +public class Startup { } +"); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Assert + var diagnostic = Assert.Single(diagnostics); + Assert.Same(DiagnosticDescriptors.DoNotUseHostConfigureServices, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.DefaultMarkerLocation, diagnostic.Location); + Assert.Equal("Suggest using builder.Services instead of ConfigureServices", diagnostic.GetMessage(CultureInfo.InvariantCulture)); + } + + [Fact] + public async Task WarnsTwiceWhenBuilderHostConfigureServicesIsUsed_Twice() + { + // Arrange + var source = TestSource.Read(@" +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +var builder = WebApplication.CreateBuilder(args); +builder.Host./*MM1*/ConfigureServices(services => +{ + services.AddAntiforgery(); +}); +builder.Host./*MM2*/ConfigureServices(services => +{ + services.AddAntiforgery(); +}); +"); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Asserts + Assert.Equal(2, diagnostics.Length); + + // First diagnostic + var firstDiagnostic = diagnostics[0]; + + Assert.Same(DiagnosticDescriptors.DoNotUseHostConfigureServices, firstDiagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MM1"], firstDiagnostic.Location); + Assert.Equal("Suggest using builder.Services instead of ConfigureServices", firstDiagnostic.GetMessage(CultureInfo.InvariantCulture)); + + // Second diagnostic + var secondDiagnostic = diagnostics[1]; + + Assert.Same(DiagnosticDescriptors.DoNotUseHostConfigureServices, secondDiagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MM2"], secondDiagnostic.Location); + Assert.Equal("Suggest using builder.Services instead of ConfigureServices", secondDiagnostic.GetMessage(CultureInfo.InvariantCulture)); + } + + [Fact] + public async Task WarnsTwiceWhenBuilderWebHostConfigureServicesIsUsed_Twice() + { + // Arrange + var source = TestSource.Read(@" +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +var builder = WebApplication.CreateBuilder(args); +builder.WebHost./*MM1*/ConfigureServices(services => +{ + services.AddAntiforgery(); +}); +builder.WebHost./*MM2*/ConfigureServices(services => +{ + services.AddAntiforgery(); +}); +"); + + // Act + var diagnostics = await Runner.GetDiagnosticsAsync(source.Source); + + // Asserts + Assert.Equal(2, diagnostics.Length); + + // First diagnostic + var firstDiagnostic = diagnostics[0]; + + Assert.Same(DiagnosticDescriptors.DoNotUseHostConfigureServices, firstDiagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MM1"], firstDiagnostic.Location); + Assert.Equal("Suggest using builder.Services instead of ConfigureServices", firstDiagnostic.GetMessage(CultureInfo.InvariantCulture)); + + // Second diagnostic + var secondDiagnostic = diagnostics[1]; + + Assert.Same(DiagnosticDescriptors.DoNotUseHostConfigureServices, secondDiagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(source.MarkerLocations["MM2"], secondDiagnostic.Location); + Assert.Equal("Suggest using builder.Services instead of ConfigureServices", secondDiagnostic.GetMessage(CultureInfo.InvariantCulture)); + } + +}