diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 5ba3256..9bf0f0c 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -4,34 +4,53 @@ about: Create a bug report to help us improve title: '' labels: bug assignees: '' - --- -**Describe the bug** -A clear and concise description of what the bug is. +### Describe the bug +A clear and concise description of what the bug is + e.g. Assigned null instead of string to `EnumClass`'s `Namespace` property and it caused exception on compilation -**Steps to Reproduce** +### Steps to Reproduce Provide simple steps to reproduce the behavior -e.g. + +e.g.: 1. Declare enum -2. Mark it with attribute -3. Build project -4. Call `xxx` method +```csharp +using EnumClass.Attributes; + +namespace Test; + +[EnumClass(ClassName = null)] +public enum SampleEnum +{ + First +} +``` +2. Build project +3. Call `xxx` method +```csharp +SampleEnum.GetAllMembers(); +``` -**Expected behavior** + +### Expected behavior A clear and concise description of what you expected to happen. + e.g. `NullReferenceExcpetion` was thrown during build by generator -**Actual behaviour** +### Actual behaviour What you expect to be done + e.g. `null` value was ommited or diagnostic reported -**Environment** +### Environment + - .NET version (6.0.103, 7.0.15) - OS (Windows, Linux) - IDE (Visual Studio, Rider) -**Logs** +### Context + Provide additional items, such as logs, that would help to solve this issue diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md index cfc1dc1..11d40f0 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -4,24 +4,23 @@ about: Suggest an idea for generator features title: '' labels: enhancement assignees: '' - --- -**Is your feature request related to a problem? Please describe** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe problem you'd like to solve or encountered** -Why do you offering this feature? May be this would be nice to have or have some useful use cases. +### Problem you'd like to solve or encountered +Why do you offering this feature? -**Describe the solution you'd like** -A clear and concise description of what you want to happen: new generator, additional functions etc... +May be this would be nice to have or have some useful use cases: +- Support for libraries +- More convenient way to use some function +- etc... -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. +### Solution you'd like +A clear and concise description of what you want to happen: + - new generator + - additional functions + - etc... -**Possible use cases** -Where this feature would be useful? Provide sample use case +### Possible use cases +Where this feature would be useful? -**Possible solution** -If you have any idea, how this feature could be implemented: extension method, static field etc... -You may leave it blank +Provide sample use case diff --git a/src/EnumClass.Core/EnumClass.Core.csproj b/src/EnumClass.Core/EnumClass.Core.csproj index 1e36ec6..3971195 100644 --- a/src/EnumClass.Core/EnumClass.Core.csproj +++ b/src/EnumClass.Core/EnumClass.Core.csproj @@ -12,7 +12,7 @@ - - + + diff --git a/src/EnumClass.Core/EnumInfoFactory.cs b/src/EnumClass.Core/EnumInfoFactory.cs index cbb4bc9..ca08935 100644 --- a/src/EnumClass.Core/EnumInfoFactory.cs +++ b/src/EnumClass.Core/EnumInfoFactory.cs @@ -3,6 +3,9 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using EnumClass.Core.Accessibility; +using EnumClass.Core.Infrastructure; +using EnumClass.Core.Models; +using EnumClass.Core.SymbolName; using EnumClass.Core.UnderlyingType; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -27,25 +30,41 @@ public static EnumInfo CreateFromNamedTypeSymbol(INamedTypeSymbol enumSymbol, { var members = enumSymbol.GetMembers(); + var attributeInfo = ExtractEnumClassAttributeCtorInfo(enumSymbol, enumClassAttribute); + var underlyingType = GetUnderlyingType(enumSymbol); + var accessibility = GetAccessibility(enumSymbol); + + var resultNamespace = GetResultNamespace(enumSymbol, attributeInfo); + var @namespace = new ManuallySpecifiedSymbolName($"global::{resultNamespace}", resultNamespace); + + var generatedClassName = GetClassName(enumSymbol, attributeInfo); + var fullyQualifiedClassName = $"global::{resultNamespace}.{generatedClassName}"; + var className = new ManuallySpecifiedSymbolName(fullyQualifiedClassName, generatedClassName); + + var fullyQualifiedEnumName = SymbolDisplay.ToDisplayString(enumSymbol, SymbolDisplayFormat.FullyQualifiedFormat); + var enumName = new ManuallySpecifiedSymbolName(fullyQualifiedEnumName, enumSymbol.Name); + var memberInfos = members - // Skip all non enum fields declarations .OfType() + .Combine(new EnumMemberInfoCreationContext(className, @namespace, enumName)) + // Skip all non enum fields declarations // Enum members are all const, according to docs - .Where(static m => m is {IsConst: true, HasConstantValue:true}) + .Where(static m => m.Left is {IsConst: true, HasConstantValue:true}) // Try to convert them into EnumMemberInfo - .Select(symbol => EnumMemberInfoFactory.CreateFromFieldSymbol(symbol, enumMemberInfoAttribute)!) + .Select(p => EnumMemberInfoFactory.CreateFromFieldSymbol(p.Left, p.Right, enumMemberInfoAttribute)!) // And skip failed .Where(static i => i is not null) // Finally, create array of members .ToArray(); - var attributeInfo = ExtractEnumClassAttributeCtorInfo(enumSymbol, enumClassAttribute); - var fullyQualifiedEnumName = SymbolDisplay.ToDisplayString(enumSymbol, SymbolDisplayFormat.FullyQualifiedFormat); - var className = GetClassName(enumSymbol, attributeInfo); - var ns = GetResultNamespace(enumSymbol, attributeInfo); - var underlyingType = GetUnderlyingType(enumSymbol); - var accessibility = GetAccessibility(enumSymbol); - var fullyQualifiedClassName = $"global::{ns}.{className}"; - return new EnumInfo(fullyQualifiedEnumName, className, fullyQualifiedClassName, ns, memberInfos, underlyingType, accessibility); + + + return new EnumInfo( + className, + enumName, + memberInfos, + underlyingType, + accessibility, + @namespace); } private static IAccessibility GetAccessibility(INamedTypeSymbol enumSymbol) diff --git a/src/EnumClass.Core/EnumMemberInfoCreationContext.cs b/src/EnumClass.Core/EnumMemberInfoCreationContext.cs new file mode 100644 index 0000000..c0e70bb --- /dev/null +++ b/src/EnumClass.Core/EnumMemberInfoCreationContext.cs @@ -0,0 +1,14 @@ +using EnumClass.Core.SymbolName; + +namespace EnumClass.Core; + +/// +/// Helpful information for constructing enum member class types +/// +/// +/// Name for generating enum class +/// +/// +/// Namespace where enum class will be generated +/// +internal readonly record struct EnumMemberInfoCreationContext(ISymbolName EnumClassName, ISymbolName Namespace, ISymbolName EnumName); \ No newline at end of file diff --git a/src/EnumClass.Core/EnumMemberInfoFactory.cs b/src/EnumClass.Core/EnumMemberInfoFactory.cs index c7ae8ec..1df8efe 100644 --- a/src/EnumClass.Core/EnumMemberInfoFactory.cs +++ b/src/EnumClass.Core/EnumMemberInfoFactory.cs @@ -1,5 +1,8 @@ using System; using System.Linq; +using EnumClass.Core.Infrastructure; +using EnumClass.Core.Models; +using EnumClass.Core.SymbolName; using Microsoft.CodeAnalysis; namespace EnumClass.Core; @@ -12,12 +15,13 @@ namespace EnumClass.Core; /// internal static class EnumMemberInfoFactory { - /// + /// /// Create enum value info with passed 'raw' values /// /// Instance of created enum value info - public static EnumMemberInfo? CreateFromFieldSymbol(IFieldSymbol fieldSymbol, - INamedTypeSymbol? enumMemberInfoAttribute) + internal static EnumMemberInfo? CreateFromFieldSymbol(IFieldSymbol fieldSymbol, + EnumMemberInfoCreationContext context, + INamedTypeSymbol? enumMemberInfoAttribute) { // For enum member this must be true if (!fieldSymbol.IsConst) @@ -26,19 +30,20 @@ internal static class EnumMemberInfoFactory } // Class name is equivalent to enum member name - var className = $"{fieldSymbol.Name}EnumValue"; - var fullyQualifiedClassName = $"{fieldSymbol.ContainingNamespace.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}.EnumClass.{className}"; + var enumClassName = $"{fieldSymbol.Name}EnumValue"; + var fullyQualifiedEnumClassName = $"{context.EnumClassName.FullyQualified}.{enumClassName}"; + + var fullyQualifiedMemberName = $"{context.EnumName.FullyQualified}.{fieldSymbol.Name}"; + var memberName = fieldSymbol.Name; - var fullyQualifiedEnumValue = $"{fieldSymbol.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}.{fieldSymbol.Name}"; - var enumMemberName = fieldSymbol.Name; var stringRepresentation = GetToStringFromValue(); var enumMemberNameWithPrefix = $"{fieldSymbol.ContainingType.Name}.{fieldSymbol.Name}"; + var integralValue = fieldSymbol.ConstantValue?.ToString() ?? throw new ArgumentNullException(); - return new EnumMemberInfo(className, - fullyQualifiedClassName, - fullyQualifiedEnumValue, - enumMemberName, + return new EnumMemberInfo( + className: new ManuallySpecifiedSymbolName(fullyQualifiedEnumClassName, enumClassName), + memberName: new ManuallySpecifiedSymbolName(fullyQualifiedMemberName, memberName), stringRepresentation, enumMemberNameWithPrefix, integralValue); @@ -46,7 +51,8 @@ internal static class EnumMemberInfoFactory string GetToStringFromValue() { // If no attributes specified, fallback to name of member - if (fieldSymbol.GetAttributes() is {Length: 0} attributes) + var attributes = fieldSymbol.GetAttributes(); + if (attributes is {Length: 0}) return fieldSymbol.Name; // Search string info in [EnumMemberInfo] diff --git a/src/EnumClass.Core/Constants.cs b/src/EnumClass.Core/Infrastructure/Constants.cs similarity index 94% rename from src/EnumClass.Core/Constants.cs rename to src/EnumClass.Core/Infrastructure/Constants.cs index 3deda09..0e4ab6c 100644 --- a/src/EnumClass.Core/Constants.cs +++ b/src/EnumClass.Core/Infrastructure/Constants.cs @@ -1,4 +1,4 @@ -namespace EnumClass.Core; +namespace EnumClass.Core.Infrastructure; public static class Constants { diff --git a/src/EnumClass.Core/Diagnostics.cs b/src/EnumClass.Core/Infrastructure/Diagnostics.cs similarity index 95% rename from src/EnumClass.Core/Diagnostics.cs rename to src/EnumClass.Core/Infrastructure/Diagnostics.cs index 97cc3e9..f942c76 100644 --- a/src/EnumClass.Core/Diagnostics.cs +++ b/src/EnumClass.Core/Infrastructure/Diagnostics.cs @@ -1,6 +1,6 @@ using Microsoft.CodeAnalysis; -namespace EnumClass.Core; +namespace EnumClass.Core.Infrastructure; public static class Diagnostics { diff --git a/src/EnumClass.Core/Infrastructure/ImmutableArrayExtensions.cs b/src/EnumClass.Core/Infrastructure/ImmutableArrayExtensions.cs new file mode 100644 index 0000000..09f89ef --- /dev/null +++ b/src/EnumClass.Core/Infrastructure/ImmutableArrayExtensions.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace EnumClass.Core.Infrastructure; + +internal static class ImmutableArrayExtensions +{ + // ReSharper disable once LoopCanBeConvertedToQuery + public static IEnumerable<(T1 Left, T2 Right)> Combine(this IEnumerable array, T2 value) + { + foreach (var element in array) + { + yield return ( element, value ); + } + } +} \ No newline at end of file diff --git a/src/EnumClass.Core/EnumInfo.cs b/src/EnumClass.Core/Models/EnumInfo.cs similarity index 88% rename from src/EnumClass.Core/EnumInfo.cs rename to src/EnumClass.Core/Models/EnumInfo.cs index bb5ce8c..89dbeb1 100644 --- a/src/EnumClass.Core/EnumInfo.cs +++ b/src/EnumClass.Core/Models/EnumInfo.cs @@ -1,36 +1,38 @@ #nullable enable -using System; using System.Collections.Generic; using System.Linq; using System.Text; using EnumClass.Core.Accessibility; +using EnumClass.Core.SymbolName; using EnumClass.Core.UnderlyingType; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -namespace EnumClass.Core; +namespace EnumClass.Core.Models; public class EnumInfo { + private readonly ISymbolName _className; + private readonly ISymbolName _enumName; + private readonly ISymbolName _namespace; + /// /// Only class name without namespace /// - public string ClassName { get; } - + public string ClassName => _className.Plain; + /// /// Fully qualified name of generated class /// - public string FullyQualifiedClassName { get; } + public string FullyQualifiedClassName => _className.FullyQualified; /// /// Fully qualified name of original enum /// - public string FullyQualifiedEnumName { get; } - + public string FullyQualifiedEnumName => _enumName.FullyQualified; + /// - /// Namespace of original enum + /// Namespace of generated class /// - public string Namespace { get; } + public string Namespace => _namespace.Plain; /// /// Members of enum @@ -48,21 +50,19 @@ public class EnumInfo /// public IAccessibility Accessibility { get; } - internal EnumInfo(string fullyQualifiedEnumName, - string className, - string fullyQualifiedClassName, - string ns, + internal EnumInfo(ISymbolName className, + ISymbolName enumName, EnumMemberInfo[] members, IUnderlyingType underlyingType, - IAccessibility accessibility) + IAccessibility accessibility, + ISymbolName @namespace) { - FullyQualifiedEnumName = fullyQualifiedEnumName; - Namespace = ns; + _className = className; + _enumName = enumName; Members = members; UnderlyingType = underlyingType; Accessibility = accessibility; - FullyQualifiedClassName = fullyQualifiedClassName; - ClassName = className; + _namespace = @namespace; } /// diff --git a/src/EnumClass.Core/EnumMemberInfo.cs b/src/EnumClass.Core/Models/EnumMemberInfo.cs similarity index 80% rename from src/EnumClass.Core/EnumMemberInfo.cs rename to src/EnumClass.Core/Models/EnumMemberInfo.cs index a278543..3263832 100644 --- a/src/EnumClass.Core/EnumMemberInfo.cs +++ b/src/EnumClass.Core/Models/EnumMemberInfo.cs @@ -1,39 +1,39 @@ #nullable enable -using System; -using System.Linq; using System.Text; -using Microsoft.CodeAnalysis; +using EnumClass.Core.SymbolName; using Microsoft.CodeAnalysis.CSharp; -namespace EnumClass.Core; +namespace EnumClass.Core.Models; public class EnumMemberInfo { + private readonly ISymbolName _className; + private readonly ISymbolName _memberName; private readonly string _stringRepresentation; - + /// /// Friendly name of class. /// To use in static fields for classes /// - public string ClassName { get; } - + public string ClassName => _className.Plain; + /// /// Class name of enum class starting with 'global::' prefix /// - public string FullyQualifiedClassName { get; } - + public string FullyQualifiedClassName => _className.FullyQualified; + /// /// Name of enum value we constructing. /// This is fully qualified, with 'global::' prefix /// - public string FullyQualifiedEnumValue { get; } + public string FullyQualifiedEnumMemberName => _memberName.FullyQualified; /// /// Name of enum member we constructing. /// This is without 'global::' prefix and enum name /// /// Cat - public string EnumMemberNameOnly { get; } + public string MemberName => _memberName.Plain; /// /// Name of enum member with original enum name prefix. @@ -48,18 +48,14 @@ public class EnumMemberInfo /// public string IntegralValue { get; } - internal EnumMemberInfo(string className, - string fullyQualifiedClassName, - string fullyQualifiedEnumValue, - string enumMemberNameOnly, + internal EnumMemberInfo(ISymbolName className, + ISymbolName memberName, string stringRepresentation, string enumMemberNameWithEnumName, string integralValue) { - ClassName = className; - FullyQualifiedClassName = fullyQualifiedClassName; - FullyQualifiedEnumValue = fullyQualifiedEnumValue; - EnumMemberNameOnly = enumMemberNameOnly; + _className = className; + _memberName = memberName; _stringRepresentation = stringRepresentation; EnumMemberNameWithEnumName = enumMemberNameWithEnumName; IntegralValue = integralValue; @@ -76,8 +72,8 @@ public string GetSwitchArgName() { return _switchArgName; } - var firstLetter = char.ToLowerInvariant(EnumMemberNameOnly[0]); - var switchArgName = $"{firstLetter}{EnumMemberNameOnly.Substring(1)}Switch"; + var firstLetter = char.ToLowerInvariant(MemberName[0]); + var switchArgName = $"{firstLetter}{MemberName.Substring(1)}Switch"; _switchArgName = switchArgName; return switchArgName; } diff --git a/src/EnumClass.Core/SymbolName/ISymbolName.cs b/src/EnumClass.Core/SymbolName/ISymbolName.cs new file mode 100644 index 0000000..1349a64 --- /dev/null +++ b/src/EnumClass.Core/SymbolName/ISymbolName.cs @@ -0,0 +1,24 @@ +namespace EnumClass.Core.SymbolName; + +/// +/// Interface for convenient work with symbol names +/// +public interface ISymbolName +{ + /// + /// Fully qualified element name. + /// Usually with 'global::' prefix + /// + /// + /// global::EnumClass.Models.PetKind - fully qualified enum name + /// + public string FullyQualified { get; } + + /// + /// Plain name of element. + /// + /// + /// PetKind - only enum name + /// + public string Plain { get; } +} \ No newline at end of file diff --git a/src/EnumClass.Core/SymbolName/ManuallySpecifiedSymbolName.cs b/src/EnumClass.Core/SymbolName/ManuallySpecifiedSymbolName.cs new file mode 100644 index 0000000..2c19936 --- /dev/null +++ b/src/EnumClass.Core/SymbolName/ManuallySpecifiedSymbolName.cs @@ -0,0 +1,13 @@ +namespace EnumClass.Core.SymbolName; + +public class ManuallySpecifiedSymbolName: ISymbolName +{ + public ManuallySpecifiedSymbolName(string fullyQualified, string plain) + { + FullyQualified = fullyQualified; + Plain = plain; + } + + public string FullyQualified { get; } + public string Plain { get; } +} \ No newline at end of file diff --git a/src/EnumClass.Generator/EnumClassIncrementalGenerator.cs b/src/EnumClass.Generator/EnumClassIncrementalGenerator.cs index b1f8c7c..bb1be79 100644 --- a/src/EnumClass.Generator/EnumClassIncrementalGenerator.cs +++ b/src/EnumClass.Generator/EnumClassIncrementalGenerator.cs @@ -2,6 +2,8 @@ using System.Diagnostics.CodeAnalysis; using System.Text; using EnumClass.Core; +using EnumClass.Core.Infrastructure; +using EnumClass.Core.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -186,14 +188,14 @@ private static void GenerateAllEnumClasses(Compilation // because usually we have only enum member name in string (my subjective opinion) foreach (var member in enumInfo.Members) { - builder.Append($" case \"{member.EnumMemberNameOnly}\":\n"); - builder.Append($" {enumVariableName} = {member.EnumMemberNameOnly};\n"); + builder.Append($" case \"{member.MemberName}\":\n"); + builder.Append($" {enumVariableName} = {member.MemberName};\n"); builder.Append($" return true;\n"); } foreach (var member in enumInfo.Members) { builder.Append($" case \"{member.EnumMemberNameWithEnumName}\":\n"); - builder.Append($" {enumVariableName} = {member.EnumMemberNameOnly};\n"); + builder.Append($" {enumVariableName} = {member.MemberName};\n"); builder.Append($" return true;\n"); } @@ -222,7 +224,7 @@ private static void GenerateAllEnumClasses(Compilation foreach (var member in enumInfo.Members) { builder.Append($" case {member.IntegralValue}:\n"); - builder.Append($" {enumVariableName} = {member.EnumMemberNameOnly};\n"); + builder.Append($" {enumVariableName} = {member.MemberName};\n"); builder.Append($" return true;\n"); } @@ -305,14 +307,14 @@ private static void GenerateAllEnumClasses(Compilation { builder.AppendLine(); // Generate static field for required Enum - builder.AppendFormat(" public static readonly {0} {1} = new {0}();\n", member.ClassName, member.EnumMemberNameOnly); + builder.AppendFormat(" public static readonly {0} {1} = new {0}();\n", member.ClassName, member.MemberName); // Generate enum class for enum builder.AppendFormat(" public partial class {0}: {1}\n", member.ClassName, enumInfo.ClassName); builder.AppendLine(" {"); // Generate constructor - builder.AppendFormat(" public {0}(): base({1}) {{ }}\n", member.ClassName, member.FullyQualifiedEnumValue); + builder.AppendFormat(" public {0}(): base({1}) {{ }}\n", member.ClassName, member.FullyQualifiedEnumMemberName); // Override default ToString() builder.AppendLine(" public override string ToString()"); @@ -369,7 +371,7 @@ private static void GenerateAllEnumClasses(Compilation foreach (var member in enumInfo.Members) { - builder.AppendFormat("{0}, ", member.EnumMemberNameOnly); + builder.AppendFormat("{0}, ", member.MemberName); } builder.AppendLine("};\n"); diff --git a/src/EnumClass.JsonConverter.Generator/JsonConverterIncrementalGenerator.cs b/src/EnumClass.JsonConverter.Generator/JsonConverterIncrementalGenerator.cs index f58a052..a5ef6a8 100644 --- a/src/EnumClass.JsonConverter.Generator/JsonConverterIncrementalGenerator.cs +++ b/src/EnumClass.JsonConverter.Generator/JsonConverterIncrementalGenerator.cs @@ -1,5 +1,6 @@ using System.Text; using EnumClass.Core; +using EnumClass.Core.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; @@ -43,6 +44,7 @@ private static void GenerateEnumJsonConverter(EnumInfo enumInfo, SourceProductio builder.AppendFormat( " public override {0} Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)\n", enumInfo.FullyQualifiedClassName); builder.AppendLine(" {"); + // We are deserializing using integral representation of enum // For this purpose, Utf8JsonSerializer has TryGet* methods in which second parts are clr name of integral type // ClrTypeName was added to IUnderlyingType primarily for such cases diff --git a/src/EnumClass.JsonConverter.Generator/icon.png b/src/EnumClass.JsonConverter.Generator/icon.png deleted file mode 100644 index 6229951..0000000 Binary files a/src/EnumClass.JsonConverter.Generator/icon.png and /dev/null differ diff --git a/tests/Generator/EnumClass.Generator.Tests/EnumClass.Generator.Tests.csproj b/tests/Generator/EnumClass.Generator.Tests/EnumClass.Generator.Tests.csproj index 010ca42..e086629 100644 --- a/tests/Generator/EnumClass.Generator.Tests/EnumClass.Generator.Tests.csproj +++ b/tests/Generator/EnumClass.Generator.Tests/EnumClass.Generator.Tests.csproj @@ -29,6 +29,7 @@ + diff --git a/tests/Generator/EnumClass.Generator.Tests/EnumClassGenerationTests.cs b/tests/Generator/EnumClass.Generator.Tests/EnumClassGeneratorTests.cs similarity index 74% rename from tests/Generator/EnumClass.Generator.Tests/EnumClassGenerationTests.cs rename to tests/Generator/EnumClass.Generator.Tests/EnumClassGeneratorTests.cs index 3766a82..0a5b64d 100644 --- a/tests/Generator/EnumClass.Generator.Tests/EnumClassGenerationTests.cs +++ b/tests/Generator/EnumClass.Generator.Tests/EnumClassGeneratorTests.cs @@ -1,15 +1,14 @@ using System.Reflection; -using System.Runtime.InteropServices; using EnumClass.Attributes; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; namespace EnumClass.Generator.Tests; -public class EnumClassGenerationTests +public class EnumClassGeneratorTests { [Fact] - public void WithSingleMember__ShouldGenerateCorrectly() + public void WithSingleMember__ShouldGenerateWithoutErrors() { var source = @"using EnumClass.Attributes; @@ -28,27 +27,27 @@ public enum SampleEnum: byte MetadataReference.CreateFromFile(Assembly.GetCallingAssembly().Location), MetadataReference.CreateFromFile(typeof(string).Assembly.Location), - // MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("System.Linq.Expressions")).Location), MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("System.Runtime")).Location), MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("netstandard")).Location), }, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); - var driver = CSharpGeneratorDriver.Create(new EnumClassIncrementalGenerator()) - .RunGenerators(compilation); + CSharpGeneratorDriver.Create(new EnumClassIncrementalGenerator()) + .RunGeneratorsAndUpdateCompilation(compilation, out _, out var diagnostics); + Assert.Empty(diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error)); } [Fact] - public void WithTwoMembers__ShouldGenerateCorrectly() + public void WithSpecifiedAttributeArguments__ShouldGenerateWithoutErrors() { var source = @"using EnumClass.Attributes; namespace Test; [EnumClass(Namespace = ""Test"", ClassName = ""SampleEnum"")] -public enum SampleEnum: long +public enum SampleEnum { - Manager = long.MaxValue - 4, + Manager, Programmer, Tester, CTO, @@ -66,7 +65,8 @@ public enum SampleEnum: long MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("netstandard")).Location), }, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); - var driver = CSharpGeneratorDriver.Create(new EnumClassIncrementalGenerator()) - .RunGenerators(compilation); + CSharpGeneratorDriver.Create(new EnumClassIncrementalGenerator()) + .RunGeneratorsAndUpdateCompilation(compilation, out _, out var diagnostics); + Assert.Empty(diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error)); } } \ No newline at end of file diff --git a/tests/Generator/EnumClass.Generator.Tests/EnumInfoFactoryTests.cs b/tests/Generator/EnumClass.Generator.Tests/EnumInfoFactoryTests.cs new file mode 100644 index 0000000..9388332 --- /dev/null +++ b/tests/Generator/EnumClass.Generator.Tests/EnumInfoFactoryTests.cs @@ -0,0 +1,146 @@ +using System.Reflection; +using EnumClass.Attributes; +using EnumClass.Core; +using EnumClass.Core.Models; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace EnumClass.Generator.Tests; + +public class EnumInfoFactoryTests +{ + public const string SampleEnumCode = @"using EnumClass.Attributes; + +namespace Test; + +[EnumClass] +public enum SampleEnum +{ + One = 1, + Two = 2, + Three = 3 +}"; + + private List GetEnumInfos(params string[] sourceCodes) + { + var compilation = CSharpCompilation.Create("Test", + syntaxTrees: sourceCodes.Select(x => CSharpSyntaxTree.ParseText(x)), + references: new[] + { + MetadataReference.CreateFromFile(typeof(object).Assembly.Location), + MetadataReference.CreateFromFile(typeof(EnumClassAttribute).Assembly.Location), + MetadataReference.CreateFromFile(typeof(EnumInfo).Assembly.Location), + MetadataReference.CreateFromFile(Assembly.GetCallingAssembly().Location), + MetadataReference.CreateFromFile(typeof(string).Assembly.Location), + MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("System.Runtime")).Location), + MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("netstandard")).Location), + }, + options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + + CSharpGeneratorDriver.Create(new EnumClassIncrementalGenerator()) + .RunGeneratorsAndUpdateCompilation(compilation, out var resultCompilation, out var d); + + // Pass SourceProductionContext safely, + // because there no errors expected + return EnumInfoFactory.GetAllEnumInfosFromCompilation(resultCompilation, new SourceProductionContext())!; + } + + [Fact] + public void GetAllEnumsFromCompilation__WithSingleMarkedEnum__ShouldReturnListWithSingleElement() + { + var enums = GetEnumInfos(SampleEnumCode); + Assert.Single(enums); + } + + [Fact] + public void GetAllEnumsFromCompilation__WithSingleMarkedEnumAnd3Members__ShouldReturnSingleEnumInfoWith3EnumMemberInfo() + { + var e = GetEnumInfos(SampleEnumCode).First(); + Assert.Equal(3, e.Members.Length); + } + + [Fact] + public void ShouldCorrectlyExtractClassName() + { + var e = GetEnumInfos(SampleEnumCode).First(); + Assert.Equal("SampleEnum", e.ClassName); + } + + [Fact] + public void ShouldCorrectlyExtractFullyQualifiedMemberClassNames() + { + var expected = new HashSet() + { + "global::Test.EnumClass.SampleEnum.OneEnumValue", + "global::Test.EnumClass.SampleEnum.TwoEnumValue", + "global::Test.EnumClass.SampleEnum.ThreeEnumValue", + }; + var e = GetEnumInfos(SampleEnumCode).First(); + var actual = e.Members.Select(x => x.FullyQualifiedClassName).ToHashSet(); + Assert.Equal(expected, actual); + } + + [Fact] + public void ShouldCorrectlyExtractMemberClassNames() + { + var expected = new HashSet() + { + "OneEnumValue", + "TwoEnumValue", + "ThreeEnumValue", + }; + var e = GetEnumInfos(SampleEnumCode).First(); + var actual = e.Members.Select(x => x.ClassName).ToHashSet(); + Assert.Equal(expected, actual); + } + + [Fact] + public void ShouldCorrectlyExtractFullyQualifiedEnumName() + { + var expected = "global::Test.SampleEnum"; + var e = GetEnumInfos(SampleEnumCode).First(); + var actual = e.FullyQualifiedEnumName; + Assert.Equal(expected, actual); + } + + [Fact] + public void ShouldCorrectlyExtractFullyQualifiedEnumMemberValueName() + { + var expected = new HashSet() + { + "global::Test.SampleEnum.One", + "global::Test.SampleEnum.Two", + "global::Test.SampleEnum.Three", + }; + var e = GetEnumInfos(SampleEnumCode).First(); + var actual = e.Members.Select(x => x.FullyQualifiedEnumMemberName).ToHashSet(); + Assert.Equal(expected, actual); + } + + [Fact] + public void ShouldCorrectlyExtractNamespace() + { + var expected = "Test.EnumClass"; + var actual = GetEnumInfos(SampleEnumCode).First().Namespace; + Assert.Equal(expected, actual); + } + + public const string SecondEnumCode = @"using EnumClass.Attributes; + +namespace Test; + +[EnumClass] +public enum SecondEnum +{ + Nope, + Yes, + No +}"; + + [Fact] + public void GetAllEnumsFromCompilation_With2MarkedEnums_ShouldReturnListWith2Elements() + { + var enums = GetEnumInfos(SampleEnumCode, SecondEnumCode); + Assert.Equal(2, enums.Count); + } +} \ No newline at end of file