From 7a85bdbec7143ac5f04a365a51fa5334f23668bf Mon Sep 17 00:00:00 2001 From: Pawel Gerr Date: Thu, 31 Aug 2023 13:18:30 +0200 Subject: [PATCH] Added new enum-gen parameter "SkipSwitchMethods" --- Directory.Build.props | 2 +- .../CodeAnalysis/SmartEnums/EnumSettings.cs | 6 +- .../SmartEnums/EnumSourceGeneratorState.cs | 3 + .../SmartEnums/SmartEnumCodeGenerator.cs | 12 +- .../SmartEnums/SmartEnumSourceGenerator.cs | 2 +- .../Extensions/AttributeDataExtensions.cs | 5 + .../EnumGenerationAttribute.cs | 5 + .../EnumSourceGeneratorTests.cs | 226 ++++++++++++++++++ 8 files changed, 254 insertions(+), 7 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 08a8f42d..29ffcf30 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ (c) $([System.DateTime]::Now.Year), Pawel Gerr. All rights reserved. - 6.2.0 + 6.3.0 Pawel Gerr true https://github.com/PawelGerr/Thinktecture.Runtime.Extensions diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSettings.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSettings.cs index 52699bb0..e6878df6 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSettings.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSettings.cs @@ -11,6 +11,7 @@ public sealed class EnumSettings : IEquatable public OperatorsGeneration EqualityComparisonOperators { get; } public bool SkipIFormattable { get; } public bool SkipToString { get; } + public bool SkipSwitchMethods { get; } public EnumSettings(AttributeData? attribute) { @@ -21,6 +22,7 @@ public EnumSettings(AttributeData? attribute) EqualityComparisonOperators = attribute?.FindEqualityComparisonOperators() ?? OperatorsGeneration.Default; SkipIFormattable = attribute?.FindSkipIFormattable() ?? false; SkipToString = attribute?.FindSkipToString() ?? false; + SkipSwitchMethods = attribute?.FindSkipSwitchMethods() ?? false; // Comparison operators depend on the equality comparison operators if (ComparisonOperators > EqualityComparisonOperators) @@ -45,7 +47,8 @@ public bool Equals(EnumSettings? other) && ComparisonOperators == other.ComparisonOperators && EqualityComparisonOperators == other.EqualityComparisonOperators && SkipIFormattable == other.SkipIFormattable - && SkipToString == other.SkipToString; + && SkipToString == other.SkipToString + && SkipSwitchMethods == other.SkipSwitchMethods; } public override int GetHashCode() @@ -59,6 +62,7 @@ public override int GetHashCode() hashCode = (hashCode * 397) ^ EqualityComparisonOperators.GetHashCode(); hashCode = (hashCode * 397) ^ SkipIFormattable.GetHashCode(); hashCode = (hashCode * 397) ^ SkipToString.GetHashCode(); + hashCode = (hashCode * 397) ^ SkipSwitchMethods.GetHashCode(); return hashCode; } diff --git a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSourceGeneratorState.cs b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSourceGeneratorState.cs index d2505b42..d0122931 100644 --- a/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSourceGeneratorState.cs +++ b/src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSourceGeneratorState.cs @@ -15,6 +15,7 @@ public sealed class EnumSourceGeneratorState : ITypeInformation, IEquatable or not. /// public bool SkipToString { get; set; } + + /// + /// Indication whether the generator should skip the implementation of the methods Switch. + /// + public bool SkipSwitchMethods { get; set; } } diff --git a/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/EnumSourceGeneratorTests.cs b/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/EnumSourceGeneratorTests.cs index 871e4293..2f7e2542 100644 --- a/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/EnumSourceGeneratorTests.cs +++ b/test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/EnumSourceGeneratorTests.cs @@ -741,6 +741,232 @@ public partial class TestEnum : IEnum AssertOutput(equalityComparisonOperators, _EQUALITY_COMPARABLE_OPERATORS_CLASS); } + [Fact] + public void Should_generate_simple_class_without_Switch() + { + /* language=c# */ + var source = @" +using System; +using Thinktecture; + +namespace Thinktecture.Tests +{ + [EnumGeneration(SkipSwitchMethods = true)] + public partial class TestEnum : IEnum + { + public static readonly TestEnum Item1 = new(""Item1""); + public static readonly TestEnum Item2 = new(""Item2""); + } +} +"; + var outputs = GetGeneratedOutputs(source, typeof(IEnum<>).Assembly); + outputs.Should().HaveCount(4); + + var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.g.cs")).Value; + var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Comparable.g.cs")).Value; + var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Parsable.g.cs")).Value; + var equalityComparisonOperators = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs")).Value; + + AssertOutput(comparableOutput, _COMPARABLE_OUTPUT_CLASS); + AssertOutput(parsableOutput, _PARSABLE_OUTPUT_CLASS_STRING_BASED); + AssertOutput(equalityComparisonOperators, _EQUALITY_COMPARABLE_OPERATORS_CLASS); + + /* language=c# */ + AssertOutput(mainOutput, _GENERATED_HEADER + """ + +namespace Thinktecture.Tests +{ + [global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter))] + partial class TestEnum : global::Thinktecture.IEnum, + global::System.IEquatable + { + [global::System.Runtime.CompilerServices.ModuleInitializer] + internal static void ModuleInit() + { + var convertFromKey = new global::System.Func(global::Thinktecture.Tests.TestEnum.Get); + global::System.Linq.Expressions.Expression> convertFromKeyExpression = static key => global::Thinktecture.Tests.TestEnum.Get(key); + + var convertToKey = new global::System.Func(static item => item.Key); + global::System.Linq.Expressions.Expression> convertToKeyExpression = static item => item.Key; + + var enumType = typeof(global::Thinktecture.Tests.TestEnum); + var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(enumType, typeof(string), true, false, convertFromKey, convertFromKeyExpression, null, convertToKey, convertToKeyExpression); + + global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(enumType, metadata); + } + + public static global::System.Collections.Generic.IEqualityComparer KeyEqualityComparer => global::System.StringComparer.OrdinalIgnoreCase; + + private static readonly global::System.Lazy> _itemsLookup + = new global::System.Lazy>(GetLookup, global::System.Threading.LazyThreadSafetyMode.PublicationOnly); + + private static readonly global::System.Lazy> _items + = new global::System.Lazy>(() => global::System.Linq.Enumerable.ToList(_itemsLookup.Value.Values).AsReadOnly(), global::System.Threading.LazyThreadSafetyMode.PublicationOnly); + + /// + /// Gets all valid items. + /// + public static global::System.Collections.Generic.IReadOnlyList Items => _items.Value; + + /// + /// The identifier of the item. + /// + public string Key { get; } + + private readonly int _hashCode; + + private TestEnum(string key) + { + ValidateConstructorArguments(ref key); + + if (key is null) + throw new global::System.ArgumentNullException(nameof(key)); + + this.Key = key; + this._hashCode = global::System.HashCode.Combine(typeof(global::Thinktecture.Tests.TestEnum), KeyEqualityComparer.GetHashCode(key)); + } + + static partial void ValidateConstructorArguments(ref string key); + + /// + /// Gets the identifier of the item. + /// + [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + string global::Thinktecture.IKeyedValueObject.GetKey() + { + return this.Key; + } + + /// + /// Gets an enumeration item for provided . + /// + /// The identifier to return an enumeration item for. + /// An instance of if is not null; otherwise null. + /// If there is no item with the provided . + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("key")] + public static global::Thinktecture.Tests.TestEnum? Get(string? key) + { + if (key is null) + return default; + + if (!_itemsLookup.Value.TryGetValue(key, out var item)) + { + throw new global::Thinktecture.UnknownEnumIdentifierException(typeof(global::Thinktecture.Tests.TestEnum), key); + } + + return item; + } + + /// + /// Gets a valid enumeration item for provided if a valid item exists. + /// + /// The identifier to return an enumeration item for. + /// A valid instance of ; otherwise null. + /// true if a valid item with provided exists; false otherwise. + public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] string key, [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out global::Thinktecture.Tests.TestEnum item) + { + if (key is null) + { + item = default; + return false; + } + + return _itemsLookup.Value.TryGetValue(key, out item); + } + + /// + /// Validates the provided and returns a valid enumeration item if found. + /// + /// The identifier to return an enumeration item for. + /// A valid instance of ; otherwise null. + /// if a valid item with provided exists; with an error message otherwise. + public static global::System.ComponentModel.DataAnnotations.ValidationResult? Validate([global::System.Diagnostics.CodeAnalysis.AllowNull] string key, [global::System.Diagnostics.CodeAnalysis.MaybeNull] out global::Thinktecture.Tests.TestEnum item) + { + if(global::Thinktecture.Tests.TestEnum.TryGet(key, out item)) + { + return global::System.ComponentModel.DataAnnotations.ValidationResult.Success; + } + else + { + return new global::System.ComponentModel.DataAnnotations.ValidationResult($"There is no item of type 'TestEnum' with the identifier '{key}'.", global::Thinktecture.SingleItem.Collection(nameof(global::Thinktecture.Tests.TestEnum.Key))); + } + } + + /// + /// Implicit conversion to the type . + /// + /// Item to covert. + /// The of provided or default if is null. + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("item")] + public static implicit operator string?(global::Thinktecture.Tests.TestEnum? item) + { + return item is null ? default : item.Key; + } + + /// + /// Explicit conversion from the type . + /// + /// Value to covert. + /// An instance of if the is a known item or implements . + [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("key")] + public static explicit operator global::Thinktecture.Tests.TestEnum?(string? key) + { + return global::Thinktecture.Tests.TestEnum.Get(key); + } + + /// + public bool Equals(global::Thinktecture.Tests.TestEnum? other) + { + return global::System.Object.ReferenceEquals(this, other); + } + + /// + public override bool Equals(object? other) + { + return other is global::Thinktecture.Tests.TestEnum item && Equals(item); + } + + /// + public override int GetHashCode() + { + return _hashCode; + } + + /// + public override string ToString() + { + return this.Key.ToString(); + } + + private static global::System.Collections.Generic.IReadOnlyDictionary GetLookup() + { + var lookup = new global::System.Collections.Generic.Dictionary(2, KeyEqualityComparer); + + void AddItem(global::Thinktecture.Tests.TestEnum item, string itemName) + { + if (item is null) + throw new global::System.ArgumentNullException($"The item \"{itemName}\" of type \"TestEnum\" must not be null."); + + if (item.Key is null) + throw new global::System.ArgumentException($"The \"Key\" of the item \"{itemName}\" of type \"TestEnum\" must not be null."); + + if (lookup.ContainsKey(item.Key)) + throw new global::System.ArgumentException($"The type \"TestEnum\" has multiple items with the identifier \"{item.Key}\"."); + + lookup.Add(item.Key, item); + } + + AddItem(Item1, nameof(Item1)); + AddItem(Item2, nameof(Item2)); + + return lookup; + } + } +} + +"""); + } + [Fact] public void Should_generate_smart_enum_with_base_class_and_non_default_constructors() {