Skip to content

Commit 7a85bdb

Browse files
committed
Added new enum-gen parameter "SkipSwitchMethods"
1 parent 8e3bea8 commit 7a85bdb

File tree

8 files changed

+254
-7
lines changed

8 files changed

+254
-7
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<Copyright>(c) $([System.DateTime]::Now.Year), Pawel Gerr. All rights reserved.</Copyright>
5-
<VersionPrefix>6.2.0</VersionPrefix>
5+
<VersionPrefix>6.3.0</VersionPrefix>
66
<Authors>Pawel Gerr</Authors>
77
<GenerateDocumentationFile>true</GenerateDocumentationFile>
88
<PackageProjectUrl>https://github.com/PawelGerr/Thinktecture.Runtime.Extensions</PackageProjectUrl>

src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSettings.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public sealed class EnumSettings : IEquatable<EnumSettings>
1111
public OperatorsGeneration EqualityComparisonOperators { get; }
1212
public bool SkipIFormattable { get; }
1313
public bool SkipToString { get; }
14+
public bool SkipSwitchMethods { get; }
1415

1516
public EnumSettings(AttributeData? attribute)
1617
{
@@ -21,6 +22,7 @@ public EnumSettings(AttributeData? attribute)
2122
EqualityComparisonOperators = attribute?.FindEqualityComparisonOperators() ?? OperatorsGeneration.Default;
2223
SkipIFormattable = attribute?.FindSkipIFormattable() ?? false;
2324
SkipToString = attribute?.FindSkipToString() ?? false;
25+
SkipSwitchMethods = attribute?.FindSkipSwitchMethods() ?? false;
2426

2527
// Comparison operators depend on the equality comparison operators
2628
if (ComparisonOperators > EqualityComparisonOperators)
@@ -45,7 +47,8 @@ public bool Equals(EnumSettings? other)
4547
&& ComparisonOperators == other.ComparisonOperators
4648
&& EqualityComparisonOperators == other.EqualityComparisonOperators
4749
&& SkipIFormattable == other.SkipIFormattable
48-
&& SkipToString == other.SkipToString;
50+
&& SkipToString == other.SkipToString
51+
&& SkipSwitchMethods == other.SkipSwitchMethods;
4952
}
5053

5154
public override int GetHashCode()
@@ -59,6 +62,7 @@ public override int GetHashCode()
5962
hashCode = (hashCode * 397) ^ EqualityComparisonOperators.GetHashCode();
6063
hashCode = (hashCode * 397) ^ SkipIFormattable.GetHashCode();
6164
hashCode = (hashCode * 397) ^ SkipToString.GetHashCode();
65+
hashCode = (hashCode * 397) ^ SkipSwitchMethods.GetHashCode();
6266

6367
return hashCode;
6468
}

src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/EnumSourceGeneratorState.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public sealed class EnumSourceGeneratorState : ITypeInformation, IEquatable<Enum
1515
public bool IsValidatable { get; }
1616
public BaseTypeState? BaseType { get; }
1717
public bool SkipToString { get; }
18+
public bool SkipSwitchMethods { get; }
1819

1920
public bool HasCreateInvalidItemImplementation { get; }
2021
public bool HasKeyComparerImplementation { get; }
@@ -33,13 +34,15 @@ public EnumSourceGeneratorState(
3334
INamedTypeSymbol type,
3435
IMemberState keyProperty,
3536
bool skipToString,
37+
bool skipSwitchMethods,
3638
bool isValidatable,
3739
bool hasCreateInvalidItemImplementation,
3840
bool hasStructLayoutAttribute,
3941
CancellationToken cancellationToken)
4042
{
4143
KeyProperty = keyProperty;
4244
SkipToString = skipToString;
45+
SkipSwitchMethods = skipSwitchMethods;
4346
IsValidatable = isValidatable;
4447
HasCreateInvalidItemImplementation = hasCreateInvalidItemImplementation;
4548
HasStructLayoutAttribute = hasStructLayoutAttribute;

src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/SmartEnumCodeGenerator.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,14 @@ public override int GetHashCode()
147147
if (!_state.SkipToString)
148148
GenerateToString();
149149

150-
GenerateSwitchForAction(false);
151-
GenerateSwitchForAction(true);
152-
GenerateSwitchForFunc(false);
153-
GenerateSwitchForFunc(true);
150+
if (!_state.SkipSwitchMethods)
151+
{
152+
GenerateSwitchForAction(false);
153+
GenerateSwitchForAction(true);
154+
GenerateSwitchForFunc(false);
155+
GenerateSwitchForFunc(true);
156+
}
157+
154158
GenerateGetLookup();
155159

156160
_sb.Append(@"

src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/SmartEnums/SmartEnumSourceGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ private bool IsEnumCandidate(TypeDeclarationSyntax typeDeclaration)
317317

318318
var attributeInfo = new AttributeInfo(type);
319319

320-
var enumState = new EnumSourceGeneratorState(factory, type, keyProperty, settings.SkipToString, isValidatable, hasCreateInvalidItemImplementation, attributeInfo.HasStructLayoutAttribute, cancellationToken);
320+
var enumState = new EnumSourceGeneratorState(factory, type, keyProperty, settings.SkipToString, settings.SkipSwitchMethods, isValidatable, hasCreateInvalidItemImplementation, attributeInfo.HasStructLayoutAttribute, cancellationToken);
321321
var derivedTypes = new SmartEnumDerivedTypes(enumState.Namespace, enumState.Name, enumState.TypeFullyQualified, enumState.IsReferenceType, FindDerivedTypes(type));
322322

323323
Logger.LogDebug("The type declaration is a valid smart enum", namespaceAndName: enumState);

src/Thinktecture.Runtime.Extensions.SourceGenerator/Extensions/AttributeDataExtensions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ public static OperatorsGeneration FindEqualityComparisonOperators(this Attribute
8080
return GetBooleanParameterValue(attributeData, "SkipToString");
8181
}
8282

83+
public static bool? FindSkipSwitchMethods(this AttributeData attributeData)
84+
{
85+
return GetBooleanParameterValue(attributeData, "SkipSwitchMethods");
86+
}
87+
8388
public static (ITypeSymbol ComparerType, ITypeSymbol ItemType)? GetComparerTypes(this AttributeData attributeData)
8489
{
8590
if (attributeData.AttributeClass is not { } attributeClass || attributeClass.TypeKind == TypeKind.Error)

src/Thinktecture.Runtime.Extensions/EnumGenerationAttribute.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,9 @@ public OperatorsGeneration EqualityComparisonOperators
7575
/// Indication whether the generator should skip the implementation of the method <see cref="object.ToString"/> or not.
7676
/// </summary>
7777
public bool SkipToString { get; set; }
78+
79+
/// <summary>
80+
/// Indication whether the generator should skip the implementation of the methods <code>Switch</code>.
81+
/// </summary>
82+
public bool SkipSwitchMethods { get; set; }
7883
}

test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/EnumSourceGeneratorTests.cs

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,232 @@ public partial class TestEnum : IEnum<string>
741741
AssertOutput(equalityComparisonOperators, _EQUALITY_COMPARABLE_OPERATORS_CLASS);
742742
}
743743

744+
[Fact]
745+
public void Should_generate_simple_class_without_Switch()
746+
{
747+
/* language=c# */
748+
var source = @"
749+
using System;
750+
using Thinktecture;
751+
752+
namespace Thinktecture.Tests
753+
{
754+
[EnumGeneration(SkipSwitchMethods = true)]
755+
public partial class TestEnum : IEnum<string>
756+
{
757+
public static readonly TestEnum Item1 = new(""Item1"");
758+
public static readonly TestEnum Item2 = new(""Item2"");
759+
}
760+
}
761+
";
762+
var outputs = GetGeneratedOutputs<SmartEnumSourceGenerator>(source, typeof(IEnum<>).Assembly);
763+
outputs.Should().HaveCount(4);
764+
765+
var mainOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.g.cs")).Value;
766+
var comparableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Comparable.g.cs")).Value;
767+
var parsableOutput = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.Parsable.g.cs")).Value;
768+
var equalityComparisonOperators = outputs.Single(kvp => kvp.Key.Contains("Thinktecture.Tests.TestEnum.EqualityComparisonOperators.g.cs")).Value;
769+
770+
AssertOutput(comparableOutput, _COMPARABLE_OUTPUT_CLASS);
771+
AssertOutput(parsableOutput, _PARSABLE_OUTPUT_CLASS_STRING_BASED);
772+
AssertOutput(equalityComparisonOperators, _EQUALITY_COMPARABLE_OPERATORS_CLASS);
773+
774+
/* language=c# */
775+
AssertOutput(mainOutput, _GENERATED_HEADER + """
776+
777+
namespace Thinktecture.Tests
778+
{
779+
[global::System.ComponentModel.TypeConverter(typeof(global::Thinktecture.ValueObjectTypeConverter<global::Thinktecture.Tests.TestEnum, string>))]
780+
partial class TestEnum : global::Thinktecture.IEnum<string, global::Thinktecture.Tests.TestEnum>,
781+
global::System.IEquatable<global::Thinktecture.Tests.TestEnum?>
782+
{
783+
[global::System.Runtime.CompilerServices.ModuleInitializer]
784+
internal static void ModuleInit()
785+
{
786+
var convertFromKey = new global::System.Func<string?, global::Thinktecture.Tests.TestEnum?>(global::Thinktecture.Tests.TestEnum.Get);
787+
global::System.Linq.Expressions.Expression<global::System.Func<string?, global::Thinktecture.Tests.TestEnum?>> convertFromKeyExpression = static key => global::Thinktecture.Tests.TestEnum.Get(key);
788+
789+
var convertToKey = new global::System.Func<global::Thinktecture.Tests.TestEnum, string>(static item => item.Key);
790+
global::System.Linq.Expressions.Expression<global::System.Func<global::Thinktecture.Tests.TestEnum, string>> convertToKeyExpression = static item => item.Key;
791+
792+
var enumType = typeof(global::Thinktecture.Tests.TestEnum);
793+
var metadata = new global::Thinktecture.Internal.KeyedValueObjectMetadata(enumType, typeof(string), true, false, convertFromKey, convertFromKeyExpression, null, convertToKey, convertToKeyExpression);
794+
795+
global::Thinktecture.Internal.KeyedValueObjectMetadataLookup.AddMetadata(enumType, metadata);
796+
}
797+
798+
public static global::System.Collections.Generic.IEqualityComparer<string?> KeyEqualityComparer => global::System.StringComparer.OrdinalIgnoreCase;
799+
800+
private static readonly global::System.Lazy<global::System.Collections.Generic.IReadOnlyDictionary<string, global::Thinktecture.Tests.TestEnum>> _itemsLookup
801+
= new global::System.Lazy<global::System.Collections.Generic.IReadOnlyDictionary<string, global::Thinktecture.Tests.TestEnum>>(GetLookup, global::System.Threading.LazyThreadSafetyMode.PublicationOnly);
802+
803+
private static readonly global::System.Lazy<global::System.Collections.Generic.IReadOnlyList<global::Thinktecture.Tests.TestEnum>> _items
804+
= new global::System.Lazy<global::System.Collections.Generic.IReadOnlyList<global::Thinktecture.Tests.TestEnum>>(() => global::System.Linq.Enumerable.ToList(_itemsLookup.Value.Values).AsReadOnly(), global::System.Threading.LazyThreadSafetyMode.PublicationOnly);
805+
806+
/// <summary>
807+
/// Gets all valid items.
808+
/// </summary>
809+
public static global::System.Collections.Generic.IReadOnlyList<global::Thinktecture.Tests.TestEnum> Items => _items.Value;
810+
811+
/// <summary>
812+
/// The identifier of the item.
813+
/// </summary>
814+
public string Key { get; }
815+
816+
private readonly int _hashCode;
817+
818+
private TestEnum(string key)
819+
{
820+
ValidateConstructorArguments(ref key);
821+
822+
if (key is null)
823+
throw new global::System.ArgumentNullException(nameof(key));
824+
825+
this.Key = key;
826+
this._hashCode = global::System.HashCode.Combine(typeof(global::Thinktecture.Tests.TestEnum), KeyEqualityComparer.GetHashCode(key));
827+
}
828+
829+
static partial void ValidateConstructorArguments(ref string key);
830+
831+
/// <summary>
832+
/// Gets the identifier of the item.
833+
/// </summary>
834+
[global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
835+
string global::Thinktecture.IKeyedValueObject<string>.GetKey()
836+
{
837+
return this.Key;
838+
}
839+
840+
/// <summary>
841+
/// Gets an enumeration item for provided <paramref name="key"/>.
842+
/// </summary>
843+
/// <param name="key">The identifier to return an enumeration item for.</param>
844+
/// <returns>An instance of <see cref="TestEnum" /> if <paramref name="key"/> is not <c>null</c>; otherwise <c>null</c>.</returns>
845+
/// <exception cref="Thinktecture.UnknownEnumIdentifierException">If there is no item with the provided <paramref name="key"/>.</exception>
846+
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("key")]
847+
public static global::Thinktecture.Tests.TestEnum? Get(string? key)
848+
{
849+
if (key is null)
850+
return default;
851+
852+
if (!_itemsLookup.Value.TryGetValue(key, out var item))
853+
{
854+
throw new global::Thinktecture.UnknownEnumIdentifierException(typeof(global::Thinktecture.Tests.TestEnum), key);
855+
}
856+
857+
return item;
858+
}
859+
860+
/// <summary>
861+
/// Gets a valid enumeration item for provided <paramref name="key"/> if a valid item exists.
862+
/// </summary>
863+
/// <param name="key">The identifier to return an enumeration item for.</param>
864+
/// <param name="item">A valid instance of <see cref="TestEnum"/>; otherwise <c>null</c>.</param>
865+
/// <returns><c>true</c> if a valid item with provided <paramref name="key"/> exists; <c>false</c> otherwise.</returns>
866+
public static bool TryGet([global::System.Diagnostics.CodeAnalysis.AllowNull] string key, [global::System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out global::Thinktecture.Tests.TestEnum item)
867+
{
868+
if (key is null)
869+
{
870+
item = default;
871+
return false;
872+
}
873+
874+
return _itemsLookup.Value.TryGetValue(key, out item);
875+
}
876+
877+
/// <summary>
878+
/// Validates the provided <paramref name="key"/> and returns a valid enumeration item if found.
879+
/// </summary>
880+
/// <param name="key">The identifier to return an enumeration item for.</param>
881+
/// <param name="item">A valid instance of <see cref="TestEnum"/>; otherwise <c>null</c>.</param>
882+
/// <returns> <see cref="System.ComponentModel.DataAnnotations.ValidationResult.Success"/> if a valid item with provided <paramref name="key"/> exists; <see cref="System.ComponentModel.DataAnnotations.ValidationResult"/> with an error message otherwise.</returns>
883+
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)
884+
{
885+
if(global::Thinktecture.Tests.TestEnum.TryGet(key, out item))
886+
{
887+
return global::System.ComponentModel.DataAnnotations.ValidationResult.Success;
888+
}
889+
else
890+
{
891+
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)));
892+
}
893+
}
894+
895+
/// <summary>
896+
/// Implicit conversion to the type <see cref="string"/>.
897+
/// </summary>
898+
/// <param name="item">Item to covert.</param>
899+
/// <returns>The <see cref="TestEnum.Key"/> of provided <paramref name="item"/> or <c>default</c> if <paramref name="item"/> is <c>null</c>.</returns>
900+
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("item")]
901+
public static implicit operator string?(global::Thinktecture.Tests.TestEnum? item)
902+
{
903+
return item is null ? default : item.Key;
904+
}
905+
906+
/// <summary>
907+
/// Explicit conversion from the type <see cref="string"/>.
908+
/// </summary>
909+
/// <param name="key">Value to covert.</param>
910+
/// <returns>An instance of <see cref="TestEnum"/> if the <paramref name="key"/> is a known item or implements <see cref="Thinktecture.IValidatableEnum{TKey}"/>.</returns>
911+
[return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull("key")]
912+
public static explicit operator global::Thinktecture.Tests.TestEnum?(string? key)
913+
{
914+
return global::Thinktecture.Tests.TestEnum.Get(key);
915+
}
916+
917+
/// <inheritdoc />
918+
public bool Equals(global::Thinktecture.Tests.TestEnum? other)
919+
{
920+
return global::System.Object.ReferenceEquals(this, other);
921+
}
922+
923+
/// <inheritdoc />
924+
public override bool Equals(object? other)
925+
{
926+
return other is global::Thinktecture.Tests.TestEnum item && Equals(item);
927+
}
928+
929+
/// <inheritdoc />
930+
public override int GetHashCode()
931+
{
932+
return _hashCode;
933+
}
934+
935+
/// <inheritdoc />
936+
public override string ToString()
937+
{
938+
return this.Key.ToString();
939+
}
940+
941+
private static global::System.Collections.Generic.IReadOnlyDictionary<string, global::Thinktecture.Tests.TestEnum> GetLookup()
942+
{
943+
var lookup = new global::System.Collections.Generic.Dictionary<string, global::Thinktecture.Tests.TestEnum>(2, KeyEqualityComparer);
944+
945+
void AddItem(global::Thinktecture.Tests.TestEnum item, string itemName)
946+
{
947+
if (item is null)
948+
throw new global::System.ArgumentNullException($"The item \"{itemName}\" of type \"TestEnum\" must not be null.");
949+
950+
if (item.Key is null)
951+
throw new global::System.ArgumentException($"The \"Key\" of the item \"{itemName}\" of type \"TestEnum\" must not be null.");
952+
953+
if (lookup.ContainsKey(item.Key))
954+
throw new global::System.ArgumentException($"The type \"TestEnum\" has multiple items with the identifier \"{item.Key}\".");
955+
956+
lookup.Add(item.Key, item);
957+
}
958+
959+
AddItem(Item1, nameof(Item1));
960+
AddItem(Item2, nameof(Item2));
961+
962+
return lookup;
963+
}
964+
}
965+
}
966+
967+
""");
968+
}
969+
744970
[Fact]
745971
public void Should_generate_smart_enum_with_base_class_and_non_default_constructors()
746972
{

0 commit comments

Comments
 (0)