From f4588bb1a20a4113b683dd70497623ca59719137 Mon Sep 17 00:00:00 2001 From: Oleksandr Liakhevych Date: Mon, 19 May 2025 13:57:57 +0300 Subject: [PATCH 1/2] Allow multiple CustomHandler attributes to be present --- .../CustomHandlerTests.cs | 103 ++++++++++++++++++ ...encyInjectionGenerator.ParseMethodModel.cs | 4 +- .../DiagnosticDescriptors.cs | 8 +- 3 files changed, 109 insertions(+), 6 deletions(-) diff --git a/ServiceScan.SourceGenerator.Tests/CustomHandlerTests.cs b/ServiceScan.SourceGenerator.Tests/CustomHandlerTests.cs index 54e1aa0..5f848a2 100644 --- a/ServiceScan.SourceGenerator.Tests/CustomHandlerTests.cs +++ b/ServiceScan.SourceGenerator.Tests/CustomHandlerTests.cs @@ -217,6 +217,109 @@ public static partial IServiceCollection ProcessServices(this IServiceCollection Assert.Equal(expected, results.GeneratedTrees[1].ToString()); } + [Fact] + public void AddMultipleCustomHandlerAttributesWithDifferentCustomHandler() + { + var source = $$""" + using ServiceScan.SourceGenerator; + + namespace GeneratorTests; + + public static partial class ServicesExtensions + { + [GenerateServiceRegistrations(AssignableTo = typeof(IFirstService), CustomHandler = nameof(HandleFirstType))] + [GenerateServiceRegistrations(AssignableTo = typeof(ISecondService), CustomHandler = nameof(HandleSecondType))] + public static partial void ProcessServices(); + + private static void HandleFirstType() => System.Console.WriteLine("First:" + typeof(T).Name); + private static void HandleSecondType() => System.Console.WriteLine("Second:" + typeof(T).Name); + } + """; + + var services = + """ + namespace GeneratorTests; + + public interface IFirstService { } + public interface ISecondService { } + public class MyService1 : IFirstService { } + public class MyService2 : ISecondService { } + """; + + var compilation = CreateCompilation(source, services); + + var results = CSharpGeneratorDriver + .Create(_generator) + .RunGenerators(compilation) + .GetRunResult(); + + var expected = $$""" + namespace GeneratorTests; + + public static partial class ServicesExtensions + { + public static partial void ProcessServices() + { + HandleFirstType(); + HandleSecondType(); + } + } + """; + Assert.Equal(expected, results.GeneratedTrees[1].ToString()); + } + + + [Fact] + public void AddMultipleCustomHandlerAttributesWithSameCustomHandler() + { + var source = $$""" + using ServiceScan.SourceGenerator; + + namespace GeneratorTests; + + public static partial class ServicesExtensions + { + [GenerateServiceRegistrations(AssignableTo = typeof(IFirstService), CustomHandler = nameof(HandleType))] + [GenerateServiceRegistrations(AssignableTo = typeof(ISecondService), CustomHandler = nameof(HandleType))] + public static partial void ProcessServices(); + + private static void HandleType() => System.Console.WriteLine(typeof(T).Name); + } + """; + + var services = + """ + namespace GeneratorTests; + + public interface IFirstService { } + public interface ISecondService { } + public class MyService1 : IFirstService { } + public class MyService2 : ISecondService { } + """; + + var compilation = CreateCompilation(source, services); + + var results = CSharpGeneratorDriver + .Create(_generator) + .RunGenerators(compilation) + .GetRunResult(); + + var expected = $$""" + namespace GeneratorTests; + + public static partial class ServicesExtensions + { + public static partial void ProcessServices() + { + HandleType(); + HandleType(); + } + } + """; + Assert.Equal(expected, results.GeneratedTrees[1].ToString()); + } + + private static Compilation CreateCompilation(params string[] source) { var path = Path.GetDirectoryName(typeof(object).Assembly.Location)!; diff --git a/ServiceScan.SourceGenerator/DependencyInjectionGenerator.ParseMethodModel.cs b/ServiceScan.SourceGenerator/DependencyInjectionGenerator.ParseMethodModel.cs index f1d4c06..96f7c8d 100644 --- a/ServiceScan.SourceGenerator/DependencyInjectionGenerator.ParseMethodModel.cs +++ b/ServiceScan.SourceGenerator/DependencyInjectionGenerator.ParseMethodModel.cs @@ -27,8 +27,8 @@ public partial class DependencyInjectionGenerator return Diagnostic.Create(MissingSearchCriteria, attribute.Location); hasCustomHandler |= attribute.CustomHandler != null; - if (hasCustomHandler && context.Attributes.Length != 1) - return Diagnostic.Create(OnlyOneCustomHandlerAllowed, attribute.Location); + if (hasCustomHandler && attribute.CustomHandler == null) + return Diagnostic.Create(CantMixRegularAndCustomHandlerRegistrations, attribute.Location); if (attribute.KeySelector != null) { diff --git a/ServiceScan.SourceGenerator/DiagnosticDescriptors.cs b/ServiceScan.SourceGenerator/DiagnosticDescriptors.cs index bfe388f..6b0a8c7 100644 --- a/ServiceScan.SourceGenerator/DiagnosticDescriptors.cs +++ b/ServiceScan.SourceGenerator/DiagnosticDescriptors.cs @@ -46,9 +46,9 @@ public static class DiagnosticDescriptors DiagnosticSeverity.Error, true); - public static readonly DiagnosticDescriptor OnlyOneCustomHandlerAllowed = new("DI0008", - "Only one GenerateServiceRegistrations attribute is allowed when CustomHandler used", - "Only one GenerateServiceRegistrations attribute is allowed when CustomHandler used", + public static readonly DiagnosticDescriptor CantMixRegularAndCustomHandlerRegistrations = new("DI0008", + "It's not allowed to mix GenerateServiceRegistrations attributes with and without CustomHandler on the same method", + "It's not allowed to mix GenerateServiceRegistrations attributes with and without CustomHandler on the same method", "Usage", DiagnosticSeverity.Error, true); @@ -69,7 +69,7 @@ public static class DiagnosticDescriptors public static readonly DiagnosticDescriptor CustomHandlerMethodHasIncorrectSignature = new("DI0011", "Provided CustomHandler method has incorrect signature", - "CustomHandler method must be generic, and must have the same parameters as the method with an attribute", + "CustomHandler method must be generic, and must have the same parameters as the method with the attribute", "Usage", DiagnosticSeverity.Error, true); From b41c1790dd145528650b650615802e0425f62519 Mon Sep 17 00:00:00 2001 From: Oleksandr Liakhevych Date: Mon, 19 May 2025 14:06:05 +0300 Subject: [PATCH 2/2] fix diagnostic conditions --- .../DependencyInjectionGenerator.ParseMethodModel.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ServiceScan.SourceGenerator/DependencyInjectionGenerator.ParseMethodModel.cs b/ServiceScan.SourceGenerator/DependencyInjectionGenerator.ParseMethodModel.cs index 96f7c8d..c4ae99b 100644 --- a/ServiceScan.SourceGenerator/DependencyInjectionGenerator.ParseMethodModel.cs +++ b/ServiceScan.SourceGenerator/DependencyInjectionGenerator.ParseMethodModel.cs @@ -16,8 +16,9 @@ public partial class DependencyInjectionGenerator if (!method.IsPartialDefinition) return Diagnostic.Create(NotPartialDefinition, method.Locations[0]); - var hasCustomHandler = false; - var attributeData = new AttributeModel[context.Attributes.Length]; + var attributeData = context.Attributes.Select(a => AttributeModel.Create(a, method)).ToArray(); + var hasCustomHandlers = attributeData.Any(a => a.CustomHandler != null); + for (var i = 0; i < context.Attributes.Length; i++) { var attribute = AttributeModel.Create(context.Attributes[i], method); @@ -26,8 +27,7 @@ public partial class DependencyInjectionGenerator if (!attribute.HasSearchCriteria) return Diagnostic.Create(MissingSearchCriteria, attribute.Location); - hasCustomHandler |= attribute.CustomHandler != null; - if (hasCustomHandler && attribute.CustomHandler == null) + if (hasCustomHandlers && attribute.CustomHandler == null) return Diagnostic.Create(CantMixRegularAndCustomHandlerRegistrations, attribute.Location); if (attribute.KeySelector != null) @@ -72,7 +72,7 @@ public partial class DependencyInjectionGenerator return null; } - if (!hasCustomHandler) + if (!hasCustomHandlers) { var serviceCollectionType = context.SemanticModel.Compilation.GetTypeByMetadataName("Microsoft.Extensions.DependencyInjection.IServiceCollection");