diff --git a/README.md b/README.md index 8687e3a..b1159fa 100644 --- a/README.md +++ b/README.md @@ -116,5 +116,5 @@ public static partial class ServiceCollectionExtensions | **AsSelf** | If true, types will be registered with their actual type. It can be combined with `AsImplementedInterfaces`. In that case implemented interfaces will be "forwarded" to an actual implementation type | | **TypeNameFilter** | Set this value to filter the types to register by their full name. You can use '*' wildcards. You can also use ',' to separate multiple filters. | | **WithAttribute** | Filter types by specified attribute type present. | -| **KeySelector** | Set this value to a static method name returning string. Returned value will be used as a key for the registration. Method should either be generic, or have a single parameter of type `Type`. | -| **CustomHandler** | Set this property to a static generic method name in the current class. This property is incompatible with `Lifetime`, `AsImplementedInterfaces`, `AsSelf`, `KeySelector` properties. | +| **KeySelector** | Set this property to add types as keyed services. This property should point to one of the following:
- Name of the static method in the current type with string return type. Method should be either generic, or have a single parameter of type `Type`.
- Const field or static property in the implementation type. | +| **CustomHandler** | Set this property to a static generic method name in the current class. This property is incompatible with `Lifetime`, `AsImplementedInterfaces`, `AsSelf`, `KeySelector` properties. | \ No newline at end of file diff --git a/ServiceScan.SourceGenerator.Tests/AddServicesTests.cs b/ServiceScan.SourceGenerator.Tests/AddServicesTests.cs index d364492..8001fd6 100644 --- a/ServiceScan.SourceGenerator.Tests/AddServicesTests.cs +++ b/ServiceScan.SourceGenerator.Tests/AddServicesTests.cs @@ -632,6 +632,42 @@ public class MyService2 : IService { } Assert.Equal(Sources.GetMethodImplementation(registrations), results.GeneratedTrees[1].ToString()); } + [Fact] + public void AddAsKeyedServices_ConstantFieldInType() + { + var attribute = @"[GenerateServiceRegistrations(AssignableTo = typeof(IService), KeySelector = ""Key"")]"; + + var compilation = CreateCompilation( + Sources.MethodWithAttribute(attribute), + """ + namespace GeneratorTests; + + public interface IService { } + + public class MyService1 : IService + { + public const string Key = "MSR1"; + } + + public class MyService2 : IService + { + public const string Key = "MSR2"; + } + """); + + var results = CSharpGeneratorDriver + .Create(_generator) + .RunGenerators(compilation) + .GetRunResult(); + + var registrations = $""" + return services + .AddKeyedTransient(GeneratorTests.MyService1.Key) + .AddKeyedTransient(GeneratorTests.MyService2.Key); + """; + Assert.Equal(Sources.GetMethodImplementation(registrations), results.GeneratedTrees[1].ToString()); + } + [Fact] public void DontGenerateAnythingIfTypeIsInvalid() { diff --git a/ServiceScan.SourceGenerator.Tests/DiagnosticTests.cs b/ServiceScan.SourceGenerator.Tests/DiagnosticTests.cs index 2ee9612..f81d6a9 100644 --- a/ServiceScan.SourceGenerator.Tests/DiagnosticTests.cs +++ b/ServiceScan.SourceGenerator.Tests/DiagnosticTests.cs @@ -202,32 +202,6 @@ public static partial class ServicesExtensions Assert.Equal(results.Diagnostics.Single().Descriptor, DiagnosticDescriptors.MissingSearchCriteria); } - [Fact] - public void KeySelectorMethodDoesNotExist() - { - var attribute = @" - private static string GetName() => typeof(T).Name.Replace(""Service"", """"); - - [GenerateServiceRegistrations(AssignableTo = typeof(IService), KeySelector = ""NoSuchMethodHere"")]"; - - var compilation = CreateCompilation( - Sources.MethodWithAttribute(attribute), - """ - namespace GeneratorTests; - - public interface IService { } - public class MyService1 : IService { } - public class MyService2 : IService { } - """); - - var results = CSharpGeneratorDriver - .Create(_generator) - .RunGenerators(compilation) - .GetRunResult(); - - Assert.Equal(results.Diagnostics.Single().Descriptor, DiagnosticDescriptors.KeySelectorMethodNotFound); - } - [Fact] public void KeySelectorMethod_GenericButHasParameters() { diff --git a/ServiceScan.SourceGenerator/DependencyInjectionGenerator.FindServicesToRegister.cs b/ServiceScan.SourceGenerator/DependencyInjectionGenerator.FindServicesToRegister.cs index 29c14f7..de7b396 100644 --- a/ServiceScan.SourceGenerator/DependencyInjectionGenerator.FindServicesToRegister.cs +++ b/ServiceScan.SourceGenerator/DependencyInjectionGenerator.FindServicesToRegister.cs @@ -61,7 +61,7 @@ private static DiagnosticModel FindServicesToRegister false, true, attribute.KeySelector, - attribute.KeySelectorGeneric); + attribute.KeySelectorType); registrations.Add(registration); } @@ -75,7 +75,7 @@ private static DiagnosticModel FindServicesToRegister shouldResolve, false, attribute.KeySelector, - attribute.KeySelectorGeneric); + attribute.KeySelectorType); registrations.Add(registration); } diff --git a/ServiceScan.SourceGenerator/DependencyInjectionGenerator.ParseMethodModel.cs b/ServiceScan.SourceGenerator/DependencyInjectionGenerator.ParseMethodModel.cs index 6f58f0b..f64de87 100644 --- a/ServiceScan.SourceGenerator/DependencyInjectionGenerator.ParseMethodModel.cs +++ b/ServiceScan.SourceGenerator/DependencyInjectionGenerator.ParseMethodModel.cs @@ -35,17 +35,17 @@ private static DiagnosticModel ParseRegisterMethodMod var keySelectorMethod = method.ContainingType.GetMembers().OfType() .FirstOrDefault(m => m.IsStatic && m.Name == attribute.KeySelector); - if (keySelectorMethod is null) - return Diagnostic.Create(KeySelectorMethodNotFound, attribute.Location); + if (keySelectorMethod is not null) + { + if (keySelectorMethod.ReturnsVoid) + return Diagnostic.Create(KeySelectorMethodHasIncorrectSignature, attribute.Location); - if (keySelectorMethod.ReturnsVoid) - return Diagnostic.Create(KeySelectorMethodHasIncorrectSignature, attribute.Location); + var validGenericKeySelector = keySelectorMethod.TypeArguments.Length == 1 && keySelectorMethod.Parameters.Length == 0; + var validNonGenericKeySelector = !keySelectorMethod.IsGenericMethod && keySelectorMethod.Parameters is [{ Type.Name: nameof(Type) }]; - var validGenericKeySelector = keySelectorMethod.TypeArguments.Length == 1 && keySelectorMethod.Parameters.Length == 0; - var validNonGenericKeySelector = !keySelectorMethod.IsGenericMethod && keySelectorMethod.Parameters is [{ Type.Name: nameof(Type) }]; - - if (!validGenericKeySelector && !validNonGenericKeySelector) - return Diagnostic.Create(KeySelectorMethodHasIncorrectSignature, attribute.Location); + if (!validGenericKeySelector && !validNonGenericKeySelector) + return Diagnostic.Create(KeySelectorMethodHasIncorrectSignature, attribute.Location); + } } if (attribute.CustomHandler != null) diff --git a/ServiceScan.SourceGenerator/DependencyInjectionGenerator.cs b/ServiceScan.SourceGenerator/DependencyInjectionGenerator.cs index 654cfb2..0d46ef6 100644 --- a/ServiceScan.SourceGenerator/DependencyInjectionGenerator.cs +++ b/ServiceScan.SourceGenerator/DependencyInjectionGenerator.cs @@ -66,18 +66,19 @@ private static string GenerateRegistrationsSource(MethodModel method, EquatableA } else { - var addMethod = registration.KeySelectorMethodName != null + var addMethod = registration.KeySelector != null ? $"AddKeyed{registration.Lifetime}" : $"Add{registration.Lifetime}"; - var keyMethodInvocation = registration.KeySelectorMethodGeneric switch + var keySelectorInvocation = registration.KeySelectorType switch { - true => $"{registration.KeySelectorMethodName}<{registration.ImplementationTypeName}>()", - false => $"{registration.KeySelectorMethodName}(typeof({registration.ImplementationTypeName}))", - null => null + KeySelectorType.GenericMethod => $"{registration.KeySelector}<{registration.ImplementationTypeName}>()", + KeySelectorType.Method => $"{registration.KeySelector}(typeof({registration.ImplementationTypeName}))", + KeySelectorType.TypeMember => $"{registration.ImplementationTypeName}.{registration.KeySelector}", + _ => null }; - return $" .{addMethod}<{registration.ServiceTypeName}, {registration.ImplementationTypeName}>({keyMethodInvocation})"; + return $" .{addMethod}<{registration.ServiceTypeName}, {registration.ImplementationTypeName}>({keySelectorInvocation})"; } } })); diff --git a/ServiceScan.SourceGenerator/DiagnosticDescriptors.cs b/ServiceScan.SourceGenerator/DiagnosticDescriptors.cs index 7f45897..bfe388f 100644 --- a/ServiceScan.SourceGenerator/DiagnosticDescriptors.cs +++ b/ServiceScan.SourceGenerator/DiagnosticDescriptors.cs @@ -39,13 +39,6 @@ public static class DiagnosticDescriptors DiagnosticSeverity.Warning, true); - public static readonly DiagnosticDescriptor KeySelectorMethodNotFound = new("DI0006", - "Provided KeySelector method is not found", - "KeySelector parameter should point to a static method in the class", - "Usage", - DiagnosticSeverity.Error, - true); - public static readonly DiagnosticDescriptor KeySelectorMethodHasIncorrectSignature = new("DI0007", "Provided KeySelector method has incorrect signature", "KeySelector should have non-void return type, and either be generic with no parameters, or non-generic with a single Type parameter", diff --git a/ServiceScan.SourceGenerator/GenerateAttributeSource.cs b/ServiceScan.SourceGenerator/GenerateAttributeSource.cs index 4e2caef..f0db928 100644 --- a/ServiceScan.SourceGenerator/GenerateAttributeSource.cs +++ b/ServiceScan.SourceGenerator/GenerateAttributeSource.cs @@ -61,9 +61,11 @@ internal class GenerateServiceRegistrationsAttribute : Attribute public string? TypeNameFilter { get; set; } /// - /// Set this property to a static method name returning string. - /// Returned value will be used as a key for the registration. - /// Method should either be generic, or have a single parameter of type . + /// Set this property to add types as keyed services. + /// This property should point to one of the following: + /// - Name of the static method in the current type with string return type. + /// Method should be either generic, or have a single parameter of type . + /// - Const field or static property in the implementation type. /// /// nameof(GetKey) public string? KeySelector { get; set; } diff --git a/ServiceScan.SourceGenerator/Model/AttributeModel.cs b/ServiceScan.SourceGenerator/Model/AttributeModel.cs index 249ae4c..91fd633 100644 --- a/ServiceScan.SourceGenerator/Model/AttributeModel.cs +++ b/ServiceScan.SourceGenerator/Model/AttributeModel.cs @@ -3,6 +3,8 @@ namespace ServiceScan.SourceGenerator.Model; +enum KeySelectorType { Method, GenericMethod, TypeMember }; + record AttributeModel( string? AssignableToTypeName, EquatableArray? AssignableToGenericArguments, @@ -11,7 +13,7 @@ record AttributeModel( string Lifetime, string? TypeNameFilter, string? KeySelector, - bool? KeySelectorGeneric, + KeySelectorType? KeySelectorType, string? CustomHandler, bool AsImplementedInterfaces, bool AsSelf, @@ -31,7 +33,7 @@ public static AttributeModel Create(AttributeData attribute, IMethodSymbol metho var keySelector = attribute.NamedArguments.FirstOrDefault(a => a.Key == "KeySelector").Value.Value as string; var customHandler = attribute.NamedArguments.FirstOrDefault(a => a.Key == "CustomHandler").Value.Value as string; - bool? keySelectorGeneric = null; + KeySelectorType? keySelectorType = null; if (keySelector != null) { var keySelectorMethod = method.ContainingType.GetMembers() @@ -40,7 +42,11 @@ public static AttributeModel Create(AttributeData attribute, IMethodSymbol metho if (keySelectorMethod != null) { - keySelectorGeneric = keySelectorMethod.IsGenericMethod; + keySelectorType = keySelectorMethod.IsGenericMethod ? Model.KeySelectorType.GenericMethod : Model.KeySelectorType.Method; + } + else + { + keySelectorType = Model.KeySelectorType.TypeMember; } } @@ -77,7 +83,7 @@ public static AttributeModel Create(AttributeData attribute, IMethodSymbol metho lifetime, typeNameFilter, keySelector, - keySelectorGeneric, + keySelectorType, customHandler, asImplementedInterfaces, asSelf, diff --git a/ServiceScan.SourceGenerator/Model/ServiceRegistrationModel.cs b/ServiceScan.SourceGenerator/Model/ServiceRegistrationModel.cs index 5b7b3e2..ef98acf 100644 --- a/ServiceScan.SourceGenerator/Model/ServiceRegistrationModel.cs +++ b/ServiceScan.SourceGenerator/Model/ServiceRegistrationModel.cs @@ -6,8 +6,8 @@ record ServiceRegistrationModel( string ImplementationTypeName, bool ResolveImplementation, bool IsOpenGeneric, - string? KeySelectorMethodName, - bool? KeySelectorMethodGeneric); + string? KeySelector, + KeySelectorType? KeySelectorType); record CustomHandlerModel( string HandlerMethodName,