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 0000000..74b197e Binary files /dev/null and b/src/AStar.Dev.Source.Generators/astar.png differ 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;