From 51e3318b33b53e0d06fcac81a0d84ebceea19a2e Mon Sep 17 00:00:00 2001 From: Jason Barden Date: Sat, 17 Jan 2026 09:20:27 +0000 Subject: [PATCH] Refactor service registration and attribute usage - Rename ServiceAttribute to AutoRegisterServiceAttribute for clarity - Add ServiceModel class and use shared ServiceLifetime enum - Generate service registration code with correct namespace - Improve attribute XML docs and endpoint registration options - Update NuGet metadata and project references - Enhance options binding logic and code style - Replace and update unit tests for service registration - Minor formatting and strong ID code improvements --- .../AutoRegisterEndpointAttribute.cs | 38 ++++++++ .../AutoRegisterOptions.cs | 8 ++ ...ute.cs => AutoRegisterServiceAttribute.cs} | 2 +- .../ServiceLifetime.cs | 3 + .../AStar.Dev.Source.Generators.csproj | 34 +++++-- .../OptionBindingGenerator.cs | 28 +++--- .../OptionsBindingCodeGenerator.cs | 7 +- .../ServiceCollectionCodeGenerator.cs | 33 ++++--- .../ServiceModel.cs | 81 ++++++++++++++++ .../ServiceRegistrationGenerator.cs | 89 ++---------------- src/AStar.Dev.Source.Generators/astar.png | Bin 0 -> 15984 bytes .../RegisterServiceAttributeShould.cs | 35 +++++++ .../ServiceAttributeShould.cs | 35 ------- ...xpectedCode#OrderId.StrongId.g.received.cs | 6 +- .../StrongIdCodeGeneration/TestStrongIds.cs | 2 +- 15 files changed, 239 insertions(+), 162 deletions(-) create mode 100644 src/AStar.Dev.Source.Generators.Attributes/AutoRegisterEndpointAttribute.cs rename src/AStar.Dev.Source.Generators.Attributes/{ServiceAttribute.cs => AutoRegisterServiceAttribute.cs} (86%) create mode 100644 src/AStar.Dev.Source.Generators/ServiceRegistrationGeneration/ServiceModel.cs create mode 100644 src/AStar.Dev.Source.Generators/astar.png create mode 100644 test/AStar.Dev.Source.Generators.Attributes.Tests.Unit/RegisterServiceAttributeShould.cs delete mode 100644 test/AStar.Dev.Source.Generators.Attributes.Tests.Unit/ServiceAttributeShould.cs diff --git a/src/AStar.Dev.Source.Generators.Attributes/AutoRegisterEndpointAttribute.cs b/src/AStar.Dev.Source.Generators.Attributes/AutoRegisterEndpointAttribute.cs new file mode 100644 index 0000000..7ee97ff --- /dev/null +++ b/src/AStar.Dev.Source.Generators.Attributes/AutoRegisterEndpointAttribute.cs @@ -0,0 +1,38 @@ +using System; +using System.Net.Http; + +namespace AStar.Dev.Source.Generators.Attributes; + +/// +/// An attribute to automatically register a minimal APU endpoint. +/// +/// +/// This attribute is used to mark classes for automatic endpoint registration in the system. +/// It supports specifying an HTTP method type and an optional method group name for categorization or grouping. +/// +[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +public class AutoRegisterEndpointAttribute(HttpMethod? methodType, string? methodGroupName = null) : Attribute +{ + /// + /// Gets the HTTP method type associated with the endpoint. + /// + /// + /// If no specific HTTP method is provided during initialization, the default value is . + /// + /// + /// Represents the HTTP method used for the endpoint, as an instance of the class. + /// + public HttpMethod MethodType { get; } = methodType ?? HttpMethod.Get; + + /// + /// Gets the group name associated with the method for organizational purposes. + /// + /// + /// This property provides a way to categorize or group methods logically within a larger structure. + /// If no group name is specified during initialization, the value will be . + /// + /// + /// Represents the name of the method group as a . Can be if not explicitly set. + /// + public string? MethodGroupName { get; } = methodGroupName; +} diff --git a/src/AStar.Dev.Source.Generators.Attributes/AutoRegisterOptions.cs b/src/AStar.Dev.Source.Generators.Attributes/AutoRegisterOptions.cs index 71a0847..1bbf91b 100644 --- a/src/AStar.Dev.Source.Generators.Attributes/AutoRegisterOptions.cs +++ b/src/AStar.Dev.Source.Generators.Attributes/AutoRegisterOptions.cs @@ -2,6 +2,14 @@ namespace AStar.Dev.Source.Generators.Attributes; +/// +/// Attribute used to indicate that a class or struct should be automatically registered as an , +/// with an optional section name provided for configuration purposes. +/// +/// +/// This attribute can only be applied to classes or structs. It is not inherited by derived types +/// and does not allow multiple usage on the same target. +/// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)] public class AutoRegisterOptionsAttribute(string? sectionName = null) : Attribute { diff --git a/src/AStar.Dev.Source.Generators.Attributes/ServiceAttribute.cs b/src/AStar.Dev.Source.Generators.Attributes/AutoRegisterServiceAttribute.cs similarity index 86% rename from src/AStar.Dev.Source.Generators.Attributes/ServiceAttribute.cs rename to src/AStar.Dev.Source.Generators.Attributes/AutoRegisterServiceAttribute.cs index 45f1ef9..46d74e1 100644 --- a/src/AStar.Dev.Source.Generators.Attributes/ServiceAttribute.cs +++ b/src/AStar.Dev.Source.Generators.Attributes/AutoRegisterServiceAttribute.cs @@ -3,7 +3,7 @@ namespace AStar.Dev.Source.Generators.Attributes; [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] -public sealed class ServiceAttribute(ServiceLifetime lifetime = ServiceLifetime.Scoped) : Attribute +public sealed class AutoRegisterServiceAttribute(ServiceLifetime lifetime = ServiceLifetime.Scoped) : Attribute { /// /// Specifies the lifetime of the service. Defaults to Scoped. diff --git a/src/AStar.Dev.Source.Generators.Attributes/ServiceLifetime.cs b/src/AStar.Dev.Source.Generators.Attributes/ServiceLifetime.cs index 0aaf428..bbf8ca2 100644 --- a/src/AStar.Dev.Source.Generators.Attributes/ServiceLifetime.cs +++ b/src/AStar.Dev.Source.Generators.Attributes/ServiceLifetime.cs @@ -1,3 +1,6 @@ namespace AStar.Dev.Source.Generators.Attributes; +/// +/// Specifies the lifetime of a service within a dependency injection container. +/// public enum ServiceLifetime { Singleton, Scoped, Transient } diff --git a/src/AStar.Dev.Source.Generators/AStar.Dev.Source.Generators.csproj b/src/AStar.Dev.Source.Generators/AStar.Dev.Source.Generators.csproj index b8edbd0..d0c53f2 100644 --- a/src/AStar.Dev.Source.Generators/AStar.Dev.Source.Generators.csproj +++ b/src/AStar.Dev.Source.Generators/AStar.Dev.Source.Generators.csproj @@ -9,26 +9,29 @@ true true AStar.Dev.Source.Generators - false true - false $(NoWarn);NU5128 - 0.1.3 - AStar Dev - AStar + 0.1.4 + AStar Development, Jason Barden + AStar Development Source generators and supporting attribute types for AStar.Dev projects. source-generator;strongid;attributes;astartools - https://example.com/AStar.Dev.Source.Generators - https://github.com/your-org/SourceGenerators1111 + https://astardevelopment.co.uk + https://github.com/astar-development/astar-dev-source-generators.git git MIT false - Initial 0.1.2 release. + v0.1.4 - Add missing namespace in generated ServiceRegistrations class. +- Rename ServiceAttribute to AutoRegisterServiceAttribute for clarity main + AStar Dev Source Generators + Readme.md + astar.png + True @@ -59,7 +62,18 @@ - - + + + + + + True + \ + + + True + \ + + diff --git a/src/AStar.Dev.Source.Generators/OptionsBindingGeneration/OptionBindingGenerator.cs b/src/AStar.Dev.Source.Generators/OptionsBindingGeneration/OptionBindingGenerator.cs index d77f728..d37dc96 100644 --- a/src/AStar.Dev.Source.Generators/OptionsBindingGeneration/OptionBindingGenerator.cs +++ b/src/AStar.Dev.Source.Generators/OptionsBindingGeneration/OptionBindingGenerator.cs @@ -61,7 +61,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var fullTypeName = ns != null ? string.Concat(ns, ".", typeName) : typeName; string? sectionName = null; AttributeData? attr = typeSymbol.GetAttributes().FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == AttrFqn); - if(attr != null && attr.ConstructorArguments.Length > 0 && attr.ConstructorArguments[0].Value is string s && !string.IsNullOrWhiteSpace(s)) + if(attr is { ConstructorArguments.Length: > 0 } && attr.ConstructorArguments[0].Value is string s && !string.IsNullOrWhiteSpace(s)) { sectionName = s; } @@ -72,23 +72,27 @@ public void Initialize(IncrementalGeneratorInitializationContext context) if(attrSyntax?.ArgumentList?.Arguments.Count > 0) { ExpressionSyntax expr = attrSyntax.ArgumentList.Arguments[0].Expression; - if(expr is LiteralExpressionSyntax literal && literal.Token.Value is string literalValue) - { - sectionName = literalValue; - } + if(expr is LiteralExpressionSyntax { Token.Value: string literalValue }) sectionName = literalValue; } } - if(string.IsNullOrWhiteSpace(sectionName)) + return !string.IsNullOrWhiteSpace(sectionName) + ? new OptionsTypeInfo(typeName, fullTypeName, sectionName!, ctx.TargetNode.GetLocation()) + : ExtractSectionNameFromMembers(ctx, typeSymbol, sectionName, typeName, fullTypeName); + } + + private static OptionsTypeInfo? ExtractSectionNameFromMembers(GeneratorAttributeSyntaxContext ctx, INamedTypeSymbol typeSymbol, string? sectionName, string typeName, string fullTypeName) + { + foreach(ISymbol member in typeSymbol.GetMembers()) { - foreach(ISymbol member in typeSymbol.GetMembers()) + if(member is not IFieldSymbol { IsStatic: true, IsConst: true, Name: "SectionName" } field || field.Type.SpecialType != SpecialType.System_String || + field.ConstantValue is not string val || string.IsNullOrWhiteSpace(val)) { - if(member is IFieldSymbol field && field.IsStatic && field.IsConst && field.Name == "SectionName" && field.Type.SpecialType == SpecialType.System_String && field.ConstantValue is string val && !string.IsNullOrWhiteSpace(val)) - { - sectionName = val; - break; - } + continue; } + + sectionName = val; + break; } return new OptionsTypeInfo(typeName, fullTypeName, sectionName ?? string.Empty, ctx.TargetNode.GetLocation()); diff --git a/src/AStar.Dev.Source.Generators/OptionsBindingGeneration/OptionsBindingCodeGenerator.cs b/src/AStar.Dev.Source.Generators/OptionsBindingGeneration/OptionsBindingCodeGenerator.cs index 1c0655b..20b4029 100644 --- a/src/AStar.Dev.Source.Generators/OptionsBindingGeneration/OptionsBindingCodeGenerator.cs +++ b/src/AStar.Dev.Source.Generators/OptionsBindingGeneration/OptionsBindingCodeGenerator.cs @@ -18,10 +18,7 @@ public static string Generate(IReadOnlyList types) _ = sb.AppendLine(" {"); _ = sb.AppendLine(" public static IServiceCollection AddAutoRegisteredOptions(this IServiceCollection services, IConfiguration configuration)"); _ = sb.AppendLine(" {"); - foreach(OptionsTypeInfo info in types) - { - _ = sb.AppendLine($" services.AddOptions<{info.FullTypeName}>()\n .Bind(configuration.GetSection(\"{EscapeString(info.SectionName)}\"))\n .ValidateDataAnnotations()\n .ValidateOnStart();"); - } + foreach(OptionsTypeInfo info in types) _ = sb.AppendLine($" services.AddOptions<{info.FullTypeName}>()\n .Bind(configuration.GetSection(\"{EscapeString(info.SectionName)}\"))\n .ValidateDataAnnotations()\n .ValidateOnStart();"); _ = sb.AppendLine(" return services;"); _ = sb.AppendLine(" }"); @@ -30,5 +27,5 @@ public static string Generate(IReadOnlyList types) return sb.ToString(); } - private static string EscapeString(string s) => s?.Replace("\\", "\\\\").Replace("\"", "\\\"") ?? string.Empty; + private static string EscapeString(string s) => s?.Replace("\\", @"\\").Replace("\"", "\\\"") ?? string.Empty; } diff --git a/src/AStar.Dev.Source.Generators/ServiceRegistrationGeneration/ServiceCollectionCodeGenerator.cs b/src/AStar.Dev.Source.Generators/ServiceRegistrationGeneration/ServiceCollectionCodeGenerator.cs index 8ae22ab..e84e74e 100644 --- a/src/AStar.Dev.Source.Generators/ServiceRegistrationGeneration/ServiceCollectionCodeGenerator.cs +++ b/src/AStar.Dev.Source.Generators/ServiceRegistrationGeneration/ServiceCollectionCodeGenerator.cs @@ -2,31 +2,31 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using AStar.Dev.Source.Generators.Attributes; namespace AStar.Dev.Source.Generators.ServiceRegistrationGeneration; internal static class ServiceCollectionCodeGenerator { - public static string Generate(IReadOnlyList items) + public static string Generate(IReadOnlyList items) { IEnumerable registrations = BuildServiceRegistrations(items); - return BuildSourceFile(registrations); + ServiceModel? item = items.FirstOrDefault(); + return BuildSourceFile(registrations, item?.Namespace ?? "AStar.Dev"); } - private static IEnumerable BuildServiceRegistrations(IReadOnlyList items) + private static IEnumerable BuildServiceRegistrations(IReadOnlyList items) { var seen = new HashSet(StringComparer.Ordinal); - foreach(ServiceRegistrationGenerator.ServiceModel model in items) + foreach(ServiceModel model in items) { foreach(var registration in CreateRegistrationsForModel(model).Where(seen.Add)) - { yield return registration; - } } } - private static IEnumerable CreateRegistrationsForModel(ServiceRegistrationGenerator.ServiceModel model) + private static IEnumerable CreateRegistrationsForModel(ServiceModel model) { var method = GetRegistrationMethod(model.Lifetime); @@ -43,20 +43,23 @@ private static IEnumerable CreateRegistrationsForModel(ServiceRegistrati } } - private static string GetRegistrationMethod(ServiceRegistrationGenerator.Lifetime lifetime) => lifetime switch - { - ServiceRegistrationGenerator.Lifetime.Singleton => "AddSingleton", - ServiceRegistrationGenerator.Lifetime.Scoped => "AddScoped", - ServiceRegistrationGenerator.Lifetime.Transient => "AddTransient", - _ => "AddScoped" - }; + private static string GetRegistrationMethod(ServiceLifetime lifetime) + => lifetime switch + { + ServiceLifetime.Singleton => "AddSingleton", + ServiceLifetime.Scoped => "AddScoped", + ServiceLifetime.Transient => "AddTransient", + _ => "AddScoped" + }; - private static string BuildSourceFile(IEnumerable registrations) + private static string BuildSourceFile(IEnumerable registrations, string @namespace) { var sb = new StringBuilder(); _ = sb.AppendLine("// "); _ = sb.AppendLine("using Microsoft.Extensions.DependencyInjection;"); _ = sb.AppendLine(); + _ = sb.AppendLine($"namespace {@namespace};"); + _ = sb.AppendLine(); _ = sb.AppendLine("public static class GeneratedServiceCollectionExtensions"); _ = sb.AppendLine("{"); _ = sb.AppendLine(" public static IServiceCollection AddAnnotatedServices(this IServiceCollection s)"); diff --git a/src/AStar.Dev.Source.Generators/ServiceRegistrationGeneration/ServiceModel.cs b/src/AStar.Dev.Source.Generators/ServiceRegistrationGeneration/ServiceModel.cs new file mode 100644 index 0000000..52030f7 --- /dev/null +++ b/src/AStar.Dev.Source.Generators/ServiceRegistrationGeneration/ServiceModel.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using System.Linq; +using AStar.Dev.Source.Generators.Attributes; +using Microsoft.CodeAnalysis; + +namespace AStar.Dev.Source.Generators.ServiceRegistrationGeneration; + +internal sealed class ServiceModel(ServiceLifetime lifetime, string implFqn, string? serviceFqn, bool alsoAsSelf, string @namespace) +{ + public ServiceLifetime Lifetime { get; } = lifetime; + public string ImplFqn { get; } = implFqn; + public string? ServiceFqn { get; } = serviceFqn; + public string? Namespace { get; } = @namespace; + public bool AlsoAsSelf { get; } = alsoAsSelf; + + public static ServiceModel? TryCreate(INamedTypeSymbol impl, AttributeData attr) + { + if(!IsValidImplementationType(impl)) + return null; + + ServiceLifetime lifetime = ExtractLifetime(attr); + INamedTypeSymbol? asType = ExtractAsType(attr); + var asSelf = ExtractAsSelf(attr); + INamedTypeSymbol? service = asType ?? InferServiceType(impl); + var ns = impl.ContainingNamespace.IsGlobalNamespace ? null : impl.ContainingNamespace.ToDisplayString(); + + // Only skip if no service and not alsoAsSelf + return service is null && !asSelf + ? null + : new ServiceModel( + lifetime: lifetime, + implFqn: impl.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), + serviceFqn: service?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), + alsoAsSelf: asSelf, + @namespace: ns + ); + } + + private static bool IsValidImplementationType(INamedTypeSymbol impl) => !impl.IsAbstract && + impl.Arity == 0 && + impl.DeclaredAccessibility == Accessibility.Public; + + private static ServiceLifetime ExtractLifetime(AttributeData attr) => attr.ConstructorArguments.Length == 1 && + attr.ConstructorArguments[0].Value is int li + ? (ServiceLifetime)li + : ServiceLifetime.Scoped; + + private static INamedTypeSymbol? ExtractAsType(AttributeData attr) + { + foreach(KeyValuePair na in attr.NamedArguments) + { + if(na.Key == "As" && na.Value.Value is INamedTypeSymbol ts) + return ts; + } + + return null; + } + + private static bool ExtractAsSelf(AttributeData attr) + { + foreach(KeyValuePair na in attr.NamedArguments) + { + if(na.Key == "AsSelf" && na.Value.Value is bool b) + return b; + } + + return false; + } + + private static INamedTypeSymbol? InferServiceType(INamedTypeSymbol impl) + { + INamedTypeSymbol[] candidates = [.. impl.AllInterfaces.Where(IsEligibleServiceInterface)]; + + return candidates.Length == 1 ? candidates[0] : null; + } + + private static bool IsEligibleServiceInterface(INamedTypeSymbol i) => i.DeclaredAccessibility == Accessibility.Public && + i.TypeKind == TypeKind.Interface && + i.Arity == 0 && + i.ToDisplayString() != "System.IDisposable"; +} diff --git a/src/AStar.Dev.Source.Generators/ServiceRegistrationGeneration/ServiceRegistrationGenerator.cs b/src/AStar.Dev.Source.Generators/ServiceRegistrationGeneration/ServiceRegistrationGenerator.cs index 13ad9a5..3aeecaa 100644 --- a/src/AStar.Dev.Source.Generators/ServiceRegistrationGeneration/ServiceRegistrationGenerator.cs +++ b/src/AStar.Dev.Source.Generators/ServiceRegistrationGeneration/ServiceRegistrationGenerator.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Collections.Immutable; +using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -9,7 +8,7 @@ namespace AStar.Dev.Source.Generators.ServiceRegistrationGeneration; [Generator] [System.Diagnostics.CodeAnalysis.SuppressMessage("MicrosoftCodeAnalysisCorrectness", "RS1038:Compiler extensions should be implemented in assemblies with compiler-provided references", Justification = "")] -public sealed class ServiceRegistrationGenerator : IIncrementalGenerator +public sealed partial class ServiceRegistrationGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { @@ -27,7 +26,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context) transform: static (syntaxCtx, _) => GetDeclaredSymbol(syntaxCtx)) .Where(static s => s is not null)!; - private static bool IsClassCandidateForServiceRegistration(SyntaxNode node) => node is ClassDeclarationSyntax c && + private static bool IsClassCandidateForServiceRegistration(SyntaxNode node) + => node is ClassDeclarationSyntax c && c.AttributeLists.Count > 0 && c.TypeParameterList is null; @@ -42,7 +42,8 @@ private static bool IsClassCandidateForServiceRegistration(SyntaxNode node) => n .Select(static (sym, _) => (sym, attr: FindServiceAttribute(sym!))) .Where(static t => t.attr is not null)!; - private static AttributeData? FindServiceAttribute(INamedTypeSymbol symbol) => symbol.GetAttributes() + private static AttributeData? FindServiceAttribute(INamedTypeSymbol symbol) + => symbol.GetAttributes() .FirstOrDefault(a => a.AttributeClass?.Name == "ServiceAttribute" || a.AttributeClass?.ToDisplayString().EndsWith(".ServiceAttribute") == true); @@ -52,85 +53,9 @@ private static bool IsClassCandidateForServiceRegistration(SyntaxNode node) => n .Select(static (t, _) => ServiceModel.TryCreate(t.sym, t.attr!)) .Where(static m => m is not null)!; - private static void GenerateSource( - SourceProductionContext spc, - (Compilation Left, ImmutableArray Right) pair) + private static void GenerateSource(SourceProductionContext spc, (Compilation Left, ImmutableArray Right) pair) { var code = ServiceCollectionCodeGenerator.Generate(pair.Right); spc.AddSource("GeneratedServiceCollectionExtensions.g.cs", code); } - - internal enum Lifetime { Singleton = 0, Scoped = 1, Transient = 2 } - - internal sealed class ServiceModel(ServiceRegistrationGenerator.Lifetime lifetime, string implFqn, string? serviceFqn, bool alsoAsSelf) - { - public Lifetime Lifetime { get; } = lifetime; - public string ImplFqn { get; } = implFqn; - public string? ServiceFqn { get; } = serviceFqn; - public bool AlsoAsSelf { get; } = alsoAsSelf; - - public static ServiceModel? TryCreate(INamedTypeSymbol impl, AttributeData attr) - { - if(!IsValidImplementationType(impl)) - return null; - - Lifetime lifetime = ExtractLifetime(attr); - INamedTypeSymbol? asType = ExtractAsType(attr); - var asSelf = ExtractAsSelf(attr); - INamedTypeSymbol? service = asType ?? InferServiceType(impl); - - // Only skip if no service and not alsoAsSelf - return service is null && !asSelf - ? null - : new ServiceModel( - lifetime: lifetime, - implFqn: impl.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), - serviceFqn: service?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), - alsoAsSelf: asSelf - ); - } - - private static bool IsValidImplementationType(INamedTypeSymbol impl) => !impl.IsAbstract && - impl.Arity == 0 && - impl.DeclaredAccessibility == Accessibility.Public; - - private static Lifetime ExtractLifetime(AttributeData attr) => attr.ConstructorArguments.Length == 1 && - attr.ConstructorArguments[0].Value is int li - ? (Lifetime)li - : Lifetime.Scoped; - - private static INamedTypeSymbol? ExtractAsType(AttributeData attr) - { - foreach(KeyValuePair na in attr.NamedArguments) - { - if(na.Key == "As" && na.Value.Value is INamedTypeSymbol ts) - return ts; - } - - return null; - } - - private static bool ExtractAsSelf(AttributeData attr) - { - foreach(KeyValuePair na in attr.NamedArguments) - { - if(na.Key == "AsSelf" && na.Value.Value is bool b) - return b; - } - - return false; - } - - private static INamedTypeSymbol? InferServiceType(INamedTypeSymbol impl) - { - INamedTypeSymbol[] candidates = [.. impl.AllInterfaces.Where(IsEligibleServiceInterface)]; - - return candidates.Length == 1 ? candidates[0] : null; - } - - private static bool IsEligibleServiceInterface(INamedTypeSymbol i) => i.DeclaredAccessibility == Accessibility.Public && - i.TypeKind == TypeKind.Interface && - i.Arity == 0 && - i.ToDisplayString() != "System.IDisposable"; - } } diff --git a/src/AStar.Dev.Source.Generators/astar.png b/src/AStar.Dev.Source.Generators/astar.png new file mode 100644 index 0000000000000000000000000000000000000000..74b197eaae17c8a9d66b29d6259ce99af25741e6 GIT binary patch literal 15984 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 literal 0 HcmV?d00001 diff --git a/test/AStar.Dev.Source.Generators.Attributes.Tests.Unit/RegisterServiceAttributeShould.cs b/test/AStar.Dev.Source.Generators.Attributes.Tests.Unit/RegisterServiceAttributeShould.cs new file mode 100644 index 0000000..284d635 --- /dev/null +++ b/test/AStar.Dev.Source.Generators.Attributes.Tests.Unit/RegisterServiceAttributeShould.cs @@ -0,0 +1,35 @@ +namespace AStar.Dev.Source.Generators.Attributes.Tests.Unit; + +public class RegisterServiceAttributeShould +{ + [Fact] + public void HaveDefaultLifetimeAsScoped() + => new AutoRegisterServiceAttribute().Lifetime.ShouldBe(ServiceLifetime.Scoped); + + [Fact] + public void AllowSettingLifetimeViaConstructor() + => new AutoRegisterServiceAttribute(ServiceLifetime.Singleton).Lifetime.ShouldBe(ServiceLifetime.Singleton); + + [Fact] + public void AllowSettingAsProperty() + => new AutoRegisterServiceAttribute { As = typeof(string) }.As.ShouldBe(typeof(string)); + + [Fact] + public void AllowSettingAsSelfProperty() + => new AutoRegisterServiceAttribute { AsSelf = true }.AsSelf.ShouldBeTrue(); + + [Fact] + public void BeApplicableToClassesOnly() + => typeof(AutoRegisterServiceAttribute).GetCustomAttributes(typeof(AttributeUsageAttribute), false) + .OfType().Single().ValidOn.ShouldBe(AttributeTargets.Class); + + [Fact] + public void NotBeInherited() + => typeof(AutoRegisterServiceAttribute).GetCustomAttributes(typeof(AttributeUsageAttribute), false) + .OfType().Single().Inherited.ShouldBeFalse(); + + [Fact] + public void NotAllowMultipleUsageOnSameClass() + => typeof(AutoRegisterServiceAttribute).GetCustomAttributes(typeof(AttributeUsageAttribute), false) + .OfType().Single().AllowMultiple.ShouldBeFalse(); +} diff --git a/test/AStar.Dev.Source.Generators.Attributes.Tests.Unit/ServiceAttributeShould.cs b/test/AStar.Dev.Source.Generators.Attributes.Tests.Unit/ServiceAttributeShould.cs deleted file mode 100644 index e03c4ff..0000000 --- a/test/AStar.Dev.Source.Generators.Attributes.Tests.Unit/ServiceAttributeShould.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace AStar.Dev.Source.Generators.Attributes.Tests.Unit; - -public class ServiceAttributeShould -{ - [Fact] - public void HaveDefaultLifetimeAsScoped() - => new ServiceAttribute().Lifetime.ShouldBe(ServiceLifetime.Scoped); - - [Fact] - public void AllowSettingLifetimeViaConstructor() - => new ServiceAttribute(ServiceLifetime.Singleton).Lifetime.ShouldBe(ServiceLifetime.Singleton); - - [Fact] - public void AllowSettingAsProperty() - => new ServiceAttribute { As = typeof(string) }.As.ShouldBe(typeof(string)); - - [Fact] - public void AllowSettingAsSelfProperty() - => new ServiceAttribute { AsSelf = true }.AsSelf.ShouldBeTrue(); - - [Fact] - public void BeApplicableToClassesOnly() - => typeof(ServiceAttribute).GetCustomAttributes(typeof(AttributeUsageAttribute), false) - .OfType().Single().ValidOn.ShouldBe(AttributeTargets.Class); - - [Fact] - public void NotBeInherited() - => typeof(ServiceAttribute).GetCustomAttributes(typeof(AttributeUsageAttribute), false) - .OfType().Single().Inherited.ShouldBeFalse(); - - [Fact] - public void NotAllowMultipleUsageOnSameClass() - => typeof(ServiceAttribute).GetCustomAttributes(typeof(AttributeUsageAttribute), false) - .OfType().Single().AllowMultiple.ShouldBeFalse(); -} diff --git a/test/AStar.Dev.Source.Generators.Tests.Unit/StrongIdCodeGeneration/StrongIdGeneratorTests.StrongId_GeneratesExpectedCode#OrderId.StrongId.g.received.cs b/test/AStar.Dev.Source.Generators.Tests.Unit/StrongIdCodeGeneration/StrongIdGeneratorTests.StrongId_GeneratesExpectedCode#OrderId.StrongId.g.received.cs index e3d3fea..db6aef9 100644 --- a/test/AStar.Dev.Source.Generators.Tests.Unit/StrongIdCodeGeneration/StrongIdGeneratorTests.StrongId_GeneratesExpectedCode#OrderId.StrongId.g.received.cs +++ b/test/AStar.Dev.Source.Generators.Tests.Unit/StrongIdCodeGeneration/StrongIdGeneratorTests.StrongId_GeneratesExpectedCode#OrderId.StrongId.g.received.cs @@ -1,6 +1,10 @@ //HintName: OrderId.StrongId.g.cs // #nullable enable +using System; + +namespace AStar.Dev.Source.Generators.Tests.Unit.StrongIdCodeGeneration; + public readonly partial struct OrderId : System.IEquatable { private readonly System.Guid _value; @@ -18,7 +22,7 @@ public static bool TryParse(string? s, out OrderId value) { - var ok = System.Guid.TryParse(s, out var g); + var ok = System.Guid.TryParse(s, out Guid g); value = ok ? new(g) : default; return ok; } diff --git a/test/AStar.Dev.Source.Generators.Tests.Unit/StrongIdCodeGeneration/TestStrongIds.cs b/test/AStar.Dev.Source.Generators.Tests.Unit/StrongIdCodeGeneration/TestStrongIds.cs index 1bf06cf..f6ef0bf 100644 --- a/test/AStar.Dev.Source.Generators.Tests.Unit/StrongIdCodeGeneration/TestStrongIds.cs +++ b/test/AStar.Dev.Source.Generators.Tests.Unit/StrongIdCodeGeneration/TestStrongIds.cs @@ -7,7 +7,7 @@ namespace AStar.Dev.Source.Generators.Tests.Unit.StrongIdCodeGeneration; public readonly partial record struct UserId; [StrongId(typeof(int))] -public readonly partial record struct OrderId; +public readonly partial record struct OrderId2; [StrongId(typeof(System.Guid))] public readonly partial record struct EntityId;