From 109aecbed8c38623319e4e7e2d820215b14a5523 Mon Sep 17 00:00:00 2001 From: Dani Michel Date: Wed, 7 Feb 2024 21:32:09 +0100 Subject: [PATCH] fix: Clarify interface generation customization --- docs/docs/30_usage.md | 22 ++++++++++++++----- .../Attribution/CapsuleAttribute.cs | 2 +- .../Attribution/CapsuleInterfaceGeneration.cs | 19 ++++++++++++++++ .../CapsuleDefinitionResolver.cs | 16 +++++++++++--- .../Capsule.Generator/InterfaceGeneration.cs | 10 +++++++++ sources/Capsule.Generator/Synchronization.cs | 2 +- .../DeviceSample/Impl/FooDevice.cs | 2 +- 7 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 sources/Capsule.Core/Attribution/CapsuleInterfaceGeneration.cs create mode 100644 sources/Capsule.Generator/InterfaceGeneration.cs diff --git a/docs/docs/30_usage.md b/docs/docs/30_usage.md index e7d29dc..0465765 100644 --- a/docs/docs/30_usage.md +++ b/docs/docs/30_usage.md @@ -23,15 +23,25 @@ The following subsections provide further details on these two parts. ### Interface Generation -Capsule generator generates a capsule interface for each capsule implementation. By default, the interface has the implementation class' name with an "I" prefix. This can be customized through the `CapsuleAttribute.InterfaceName` property. +Capsule generator is able to generate a capsule interface for each capsule implementation. -Also, you can bring your own capsule interface. Capsule uses the following logic to decide interface generation: +By default, Capsule considers the list of implemented interfaces on the Capsule implementation: -1. If `CapsuleAttribute.GenerateInterface` is specified, that value determines if an interface is generated. -1. If the implementation implements exactly one interface that is not `[CapsuleIgnore]` attributed, that interface will be used and no additional interface will be generated. -1. Otherwise, the generator defaults to generating an interface. +- If there is exactly one interface that is not `[CapsuleIgnore]` attributed, that interface is used and no additional interface is generated. +- Otherwise, an interface with the same name as the implementation, but prefixed with an "I", will be generated. -If you provide the interface yourself, you'll need to ensure it matches the exposed methods and properties. +Interface generation can be customized through `CapsuleAttribute.InterfaceGeneration`: + +- `Enable`: Generate an interface. +- `Disable`: Do not generate an interface. +- `Auto`: The default behavior described above applied. + +The interface name can be customized through `CapsuleAttribute.InterfaceName`: + +- If this property is specified and non-null, that interface name will be used. +- Otherwise, the default behavior applies. + +If you bring your own interface, you'll need to ensure it matches the exposed methods and properties. ### Exposing Methods & Properties diff --git a/sources/Capsule.Core/Attribution/CapsuleAttribute.cs b/sources/Capsule.Core/Attribution/CapsuleAttribute.cs index 53292d0..026442c 100644 --- a/sources/Capsule.Core/Attribution/CapsuleAttribute.cs +++ b/sources/Capsule.Core/Attribution/CapsuleAttribute.cs @@ -16,5 +16,5 @@ public sealed class CapsuleAttribute : Attribute /// /// Whether or not to generate the capsule interface. /// - public bool GenerateInterface { get; init; } = true; + public CapsuleInterfaceGeneration InterfaceGeneration { get; init; } = CapsuleInterfaceGeneration.Auto; } diff --git a/sources/Capsule.Core/Attribution/CapsuleInterfaceGeneration.cs b/sources/Capsule.Core/Attribution/CapsuleInterfaceGeneration.cs new file mode 100644 index 0000000..7812305 --- /dev/null +++ b/sources/Capsule.Core/Attribution/CapsuleInterfaceGeneration.cs @@ -0,0 +1,19 @@ +namespace Capsule.Attribution; + +public enum CapsuleInterfaceGeneration +{ + /// + /// Do not generate Capsule interface if there is a single candidate interface present. Generate otherwise. + /// + Auto, + + /// + /// Do not generate Capsule interface. + /// + Disable, + + /// + /// Generate Capsule interface. + /// + Enable +} diff --git a/sources/Capsule.Generator/CapsuleDefinitionResolver.cs b/sources/Capsule.Generator/CapsuleDefinitionResolver.cs index a2e3e5d..a85df8e 100644 --- a/sources/Capsule.Generator/CapsuleDefinitionResolver.cs +++ b/sources/Capsule.Generator/CapsuleDefinitionResolver.cs @@ -6,7 +6,7 @@ internal class CapsuleDefinitionResolver { private const string InterfaceNamePropertyName = "InterfaceName"; - private const string GenerateInterfacePropertyName = "GenerateInterface"; + private const string InterfaceGenerationPropertyName = "InterfaceGeneration"; public CapsuleDefinition GetCapsuleDefinition(INamedTypeSymbol classSymbol) { @@ -25,9 +25,19 @@ public CapsuleDefinition GetCapsuleDefinition(INamedTypeSymbol classSymbol) var interfaceName = capsuleAttribute.GetProperty(InterfaceNamePropertyName)?.Value as string ?? singleInterface?.Name ?? "I" + classSymbol.Name; - var generateInterface = capsuleAttribute.GetProperty(GenerateInterfacePropertyName)?.Value as bool? ?? - singleInterface == null; + var interfaceGeneration = DetermineInterfaceGeneration(capsuleAttribute) ?? + (singleInterface == null + ? InterfaceGeneration.Enable + : InterfaceGeneration.Disable); + + var generateInterface = interfaceGeneration == InterfaceGeneration.Enable || + (interfaceGeneration == InterfaceGeneration.Auto && singleInterface == null); return new(interfaceName, generateInterface); } + + private static InterfaceGeneration? DetermineInterfaceGeneration(AttributeData capsuleAttribute) => + capsuleAttribute.GetProperty(InterfaceGenerationPropertyName)?.Value is int intVal + ? (InterfaceGeneration)intVal + : null; } diff --git a/sources/Capsule.Generator/InterfaceGeneration.cs b/sources/Capsule.Generator/InterfaceGeneration.cs new file mode 100644 index 0000000..0493c7d --- /dev/null +++ b/sources/Capsule.Generator/InterfaceGeneration.cs @@ -0,0 +1,10 @@ +namespace Capsule.Generator; + +internal enum InterfaceGeneration +{ + // Enum int values must match those in CapsuleInterfaceGeneration. + + Auto, + Disable, + Enable +} diff --git a/sources/Capsule.Generator/Synchronization.cs b/sources/Capsule.Generator/Synchronization.cs index fcfa49e..2e88d71 100644 --- a/sources/Capsule.Generator/Synchronization.cs +++ b/sources/Capsule.Generator/Synchronization.cs @@ -3,7 +3,7 @@ internal enum Synchronization { // Enum literals must match method names in CapsuleSynchronizer, and int values - // must match those in CapsulSynchronization. + // must match those in CapsuleSynchronization. EnqueueAwaitResult, EnqueueAwaitReception, diff --git a/sources/Capsule.Test/AutomatedTests/DeviceSample/Impl/FooDevice.cs b/sources/Capsule.Test/AutomatedTests/DeviceSample/Impl/FooDevice.cs index 4bf687d..ffc6ae9 100644 --- a/sources/Capsule.Test/AutomatedTests/DeviceSample/Impl/FooDevice.cs +++ b/sources/Capsule.Test/AutomatedTests/DeviceSample/Impl/FooDevice.cs @@ -4,7 +4,7 @@ namespace Capsule.Test.AutomatedTests.DeviceSample.Impl; -[Capsule(InterfaceName = nameof(IDevice), GenerateInterface = false)] +[Capsule(InterfaceName = nameof(IDevice), InterfaceGeneration = CapsuleInterfaceGeneration.Disable)] public class FooDevice : IDevice, CapsuleFeature.IInitializer, CapsuleFeature.ITimers { // ReSharper disable once NotAccessedField.Local