Skip to content

Commit 313434f

Browse files
authored
Implement EnumMetadataGenerator and Registry; remove EnumHelper class (#321)
Implement EnumMetadataGenerator and EnumMetadataRegistry for enhanced enum metadata handling; remove obsolete EnumHelper class
1 parent 55882f3 commit 313434f

File tree

5 files changed

+196
-48
lines changed

5 files changed

+196
-48
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
using System.Linq;
2+
using System.Text;
3+
4+
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.CSharp.Syntax;
6+
7+
namespace Sharprompt.SourceGenerator;
8+
9+
[Generator(LanguageNames.CSharp)]
10+
public sealed class EnumMetadataGenerator : IIncrementalGenerator
11+
{
12+
private static readonly SymbolDisplayFormat s_fullyQualifiedFormat = SymbolDisplayFormat.FullyQualifiedFormat;
13+
14+
public void Initialize(IncrementalGeneratorInitializationContext context)
15+
{
16+
var provider = context.SyntaxProvider
17+
.CreateSyntaxProvider(
18+
predicate: static (node, _) => node is EnumDeclarationSyntax,
19+
transform: static (syntaxContext, cancellationToken) => (INamedTypeSymbol?)syntaxContext.SemanticModel.GetDeclaredSymbol((EnumDeclarationSyntax)syntaxContext.Node, cancellationToken))
20+
.Where(static symbol => symbol is not null);
21+
22+
context.RegisterSourceOutput(provider, static (productionContext, symbol) =>
23+
{
24+
var enumSymbol = symbol!;
25+
var source = GenerateSource(enumSymbol);
26+
if (source.Length == 0)
27+
{
28+
return;
29+
}
30+
31+
var hintName = enumSymbol.ToDisplayString()
32+
.Replace('<', '_')
33+
.Replace('>', '_')
34+
.Replace(',', '_')
35+
.Replace(' ', '_')
36+
.Replace('.', '_')
37+
+ ".EnumMetadata.g.cs";
38+
39+
productionContext.AddSource(hintName, source);
40+
});
41+
}
42+
43+
private static string GenerateSource(INamedTypeSymbol enumSymbol)
44+
{
45+
var members = enumSymbol.GetMembers()
46+
.OfType<IFieldSymbol>()
47+
.Where(field => field.HasConstantValue)
48+
.Select((field, index) => AnalyzeMember(enumSymbol, field, index))
49+
.ToArray();
50+
51+
if (members.Length == 0)
52+
{
53+
return string.Empty;
54+
}
55+
56+
var orderedMembers = members
57+
.OrderBy(member => member.Order ?? int.MaxValue)
58+
.ThenBy(member => member.Index)
59+
.ToArray();
60+
61+
var enumTypeName = enumSymbol.ToDisplayString(s_fullyQualifiedFormat);
62+
var typeIdentifier = Sanitize(enumSymbol.ToDisplayString());
63+
64+
var builder = new StringBuilder();
65+
builder.AppendLine("// <auto-generated/>");
66+
builder.AppendLine("#nullable enable");
67+
builder.AppendLine();
68+
builder.AppendLine("file static class __SharpromptEnumMetadata_" + typeIdentifier);
69+
builder.AppendLine("{");
70+
builder.AppendLine(" [global::System.Runtime.CompilerServices.ModuleInitializer]");
71+
builder.AppendLine(" internal static void Register()");
72+
builder.AppendLine(" {");
73+
builder.AppendLine(" global::Sharprompt.EnumMetadataRegistry.Register<" + enumTypeName + ">(");
74+
builder.AppendLine(" new " + enumTypeName + "[]");
75+
builder.AppendLine(" {");
76+
77+
for (var i = 0; i < orderedMembers.Length; i++)
78+
{
79+
var suffix = i == orderedMembers.Length - 1 ? string.Empty : ",";
80+
builder.AppendLine(" " + enumTypeName + "." + orderedMembers[i].Name + suffix);
81+
}
82+
83+
builder.AppendLine(" },");
84+
builder.AppendLine(" value => value switch");
85+
builder.AppendLine(" {");
86+
87+
foreach (var member in members)
88+
{
89+
builder.AppendLine(" " + enumTypeName + "." + member.Name + " => " + Quote(member.DisplayName) + ",");
90+
}
91+
92+
builder.AppendLine(" _ => value.ToString()");
93+
builder.AppendLine(" });");
94+
builder.AppendLine(" }");
95+
builder.AppendLine("}");
96+
97+
return builder.ToString();
98+
}
99+
100+
private static EnumMemberMetadata AnalyzeMember(INamedTypeSymbol enumSymbol, IFieldSymbol field, int index)
101+
{
102+
var displayName = field.Name;
103+
int? order = null;
104+
105+
var displayAttribute = field.GetAttributes()
106+
.FirstOrDefault(attribute => attribute.AttributeClass?.ToDisplayString() == "System.ComponentModel.DataAnnotations.DisplayAttribute");
107+
108+
if (displayAttribute != null)
109+
{
110+
foreach (var namedArgument in displayAttribute.NamedArguments)
111+
{
112+
if (namedArgument.Key == "Name" && namedArgument.Value.Value is string name)
113+
{
114+
displayName = name;
115+
}
116+
else if (namedArgument.Key == "Order" && namedArgument.Value.Value is int value)
117+
{
118+
order = value;
119+
}
120+
}
121+
}
122+
123+
return new EnumMemberMetadata(field.Name, displayName, order, index);
124+
}
125+
126+
private static string Quote(string text)
127+
{
128+
return "\"" + text.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n").Replace("\r", "\\r") + "\"";
129+
}
130+
131+
private static string Sanitize(string name)
132+
{
133+
var builder = new StringBuilder(name.Length);
134+
135+
foreach (var c in name)
136+
{
137+
builder.Append(char.IsLetterOrDigit(c) ? c : '_');
138+
}
139+
140+
return builder.ToString();
141+
}
142+
143+
private sealed class EnumMemberMetadata
144+
{
145+
public EnumMemberMetadata(string name, string displayName, int? order, int index)
146+
{
147+
Name = name;
148+
DisplayName = displayName;
149+
Order = order;
150+
Index = index;
151+
}
152+
153+
public string Name { get; }
154+
public string DisplayName { get; }
155+
public int? Order { get; }
156+
public int Index { get; }
157+
}
158+
}

Sharprompt/EnumMetadataRegistry.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using System.Collections.Concurrent;
3+
using System.Collections.Generic;
4+
5+
namespace Sharprompt;
6+
7+
public static class EnumMetadataRegistry
8+
{
9+
private static readonly ConcurrentDictionary<Type, object> s_metadata = new();
10+
11+
public static void Register<TEnum>(IReadOnlyList<TEnum> values, Func<TEnum, string> textSelector) where TEnum : notnull
12+
{
13+
s_metadata[typeof(TEnum)] = new EnumMetadata<TEnum>(values, textSelector);
14+
}
15+
16+
internal static bool TryGet<TEnum>(out IReadOnlyList<TEnum> values, out Func<TEnum, string> textSelector) where TEnum : notnull
17+
{
18+
if (s_metadata.TryGetValue(typeof(TEnum), out var metadata))
19+
{
20+
var item = (EnumMetadata<TEnum>)metadata;
21+
values = item.Values;
22+
textSelector = item.TextSelector;
23+
return true;
24+
}
25+
26+
values = null!;
27+
textSelector = null!;
28+
return false;
29+
}
30+
31+
private sealed record EnumMetadata<TEnum>(IReadOnlyList<TEnum> Values, Func<TEnum, string> TextSelector);
32+
}

Sharprompt/Internal/EnumHelper.cs

Lines changed: 0 additions & 40 deletions
This file was deleted.

Sharprompt/MultiSelectOptions.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Collections.Generic;
33

4-
using Sharprompt.Internal;
54
using Sharprompt.Strings;
65

76
namespace Sharprompt;
@@ -10,10 +9,10 @@ public class MultiSelectOptions<T> where T : notnull
109
{
1110
public MultiSelectOptions()
1211
{
13-
if (typeof(T).IsAssignableTo(typeof(Enum)))
12+
if (EnumMetadataRegistry.TryGet<T>(out var values, out var textSelector))
1413
{
15-
Items = EnumHelper<T>.GetValues();
16-
TextSelector = EnumHelper<T>.GetDisplayName;
14+
Items = values;
15+
TextSelector = textSelector;
1716
}
1817
}
1918

Sharprompt/SelectOptions.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Collections.Generic;
33

4-
using Sharprompt.Internal;
54
using Sharprompt.Strings;
65

76
namespace Sharprompt;
@@ -10,10 +9,10 @@ public class SelectOptions<T> where T : notnull
109
{
1110
public SelectOptions()
1211
{
13-
if (typeof(T).IsAssignableTo(typeof(Enum)))
12+
if (EnumMetadataRegistry.TryGet<T>(out var values, out var textSelector))
1413
{
15-
Items = EnumHelper<T>.GetValues();
16-
TextSelector = EnumHelper<T>.GetDisplayName;
14+
Items = values;
15+
TextSelector = textSelector;
1716
}
1817
}
1918

0 commit comments

Comments
 (0)