Skip to content

Commit

Permalink
Add EnumTypeFor attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
OctopBP committed Mar 19, 2024
1 parent a3fcef2 commit d6aea2c
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 1 deletion.
6 changes: 6 additions & 0 deletions UnityAttributes/EnumTypeFor/EnumMemberToProcess.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace UnityAttributes.EnumTypeFor;

internal sealed record EnumMemberToProcess(string Name)
{
public string Name { get; } = Name;
}
20 changes: 20 additions & 0 deletions UnityAttributes/EnumTypeFor/EnumToProcess.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#nullable enable
using System.Collections.Generic;
using Microsoft.CodeAnalysis;

namespace UnityAttributes.EnumTypeFor;

internal sealed record EnumToProcess(ITypeSymbol EnumSymbol, ISymbol ForTypeSymbol, List<EnumMemberToProcess> Members,
string? FullNamespace, bool ShortName)
{
public string FullCsharpName { get; } = EnumSymbol.ToDisplayString();
public string DocumentationId { get; } = DocumentationCommentId.CreateDeclarationId(EnumSymbol);
public bool ShortName { get; } = ShortName;

public ITypeSymbol EnumSymbol { get; } = EnumSymbol;
public ISymbol ForTypeSymbol { get; } = ForTypeSymbol;
public List<EnumMemberToProcess> Members { get; } = Members;
public string? FullNamespace { get; } = FullNamespace;
public string ClassName { get; } =
$"{EnumSymbol.Name}For{(ShortName ? ForTypeSymbol.Name : ForTypeSymbol.ToDisplayString().Replace(".", "_"))}";
}
25 changes: 25 additions & 0 deletions UnityAttributes/EnumTypeFor/EnumTypeForAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace UnityAttributes.EnumTypeFor;

public static class EnumTypeForAttribute
{
public const string ATTRIBUTE_SHORT_NAME = "EnumTypeFor";
public const string ATTRIBUTE_FULL_NAME = ATTRIBUTE_SHORT_NAME + "Attribute";

public const string ATTRIBUTE_TEXT =
$$"""
/// <auto-generated />
[global::System.AttributeUsage(global::System.AttributeTargets.Enum, Inherited = true, AllowMultiple = true)]
internal sealed class {{ATTRIBUTE_FULL_NAME}} : global::System.Attribute
{
private System.Type _type;
private bool _shortName;
public {{ATTRIBUTE_FULL_NAME}}(System.Type type, bool shortName = true)
{
_type = type;
_shortName = shortName;
}
}
""";
}
156 changes: 156 additions & 0 deletions UnityAttributes/EnumTypeFor/EnumTypeForGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
using System.Collections.Generic;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using UnityAttributes.Common;

namespace UnityAttributes.EnumTypeFor;

[Generator]
public sealed class EnumTypeForGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var enums = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: static (node, _) => IsSyntaxTargetForGeneration(node),
transform: static (syntaxContext, token) => GetSemanticTargetForGeneration(syntaxContext, token))
.SelectMany(static (array, _) => array)
.Collect()
.SelectMany(static (array, _) => array);

context.RegisterPostInitializationOutput(i => i.AddSource(
$"{EnumTypeForAttribute.ATTRIBUTE_FULL_NAME}.g", EnumTypeForAttribute.ATTRIBUTE_TEXT));

context.RegisterSourceOutput(enums, GenerateCode);
}

private static bool IsSyntaxTargetForGeneration(SyntaxNode node)
{
return node is EnumDeclarationSyntax;
}

private static List<EnumToProcess> GetSemanticTargetForGeneration(GeneratorSyntaxContext ctx,
CancellationToken token)
{
var enumDeclarationSyntax = (EnumDeclarationSyntax) ctx.Node;

var enumDeclarationSymbol = ctx.SemanticModel.GetDeclaredSymbol(enumDeclarationSyntax, token);
if (enumDeclarationSymbol is not ITypeSymbol enumDeclarationTypeSymbol)
{
return [];
}

var enumNamespace = enumDeclarationTypeSymbol.GetNamespace();

var membersToProcess = new List<EnumMemberToProcess>();
foreach (var enumMemberDeclarationSyntax in enumDeclarationSyntax.Members)
{
membersToProcess.Add(new EnumMemberToProcess(enumMemberDeclarationSyntax.Identifier.Text));
}

var list = new List<EnumToProcess>();
foreach (var attributeSyntax in enumDeclarationSyntax.AllAttributesWhere
(syntax => syntax.Name.IsEqualByName(EnumTypeForAttribute.ATTRIBUTE_SHORT_NAME)))
{
if (attributeSyntax.ArgumentList is null)
{
continue;
}

var arguments = attributeSyntax.ArgumentList.Arguments;
if (arguments.Count == 0)
{
continue;
}

if (arguments[0].Expression is not TypeOfExpressionSyntax typeOfExpressionSyntax)
{
continue;
}

var forTypeSymbol = ctx.SemanticModel.GetSymbolInfo(typeOfExpressionSyntax.Type).Symbol;
if (forTypeSymbol is null)
{
continue;
}

var shortName = true;
if (arguments.Count > 1)
{
if (arguments[1].Expression is LiteralExpressionSyntax { Token.Text: "false" })
{
shortName = false;
}
}

list.Add(new EnumToProcess(
enumDeclarationTypeSymbol, forTypeSymbol, membersToProcess, enumNamespace, shortName));
}

return list;
}

private static void GenerateCode(SourceProductionContext context, EnumToProcess enumToProcess)
{
var code = GenerateCode(enumToProcess);
context.AddSource($"{enumToProcess.FullCsharpName}_for_{enumToProcess.ForTypeSymbol.ToDisplayString()}.g",
SourceText.From(code, Encoding.UTF8));
}

private static string GenerateCode(EnumToProcess enumToProcess)
{
var builder = new CodeBuilder();

var isVisible = enumToProcess.EnumSymbol.IsVisibleOutsideOfAssembly();
var methodVisibility = isVisible ? "public" : "internal";

var typeName = enumToProcess.ForTypeSymbol.ToDisplayString();

builder.AppendLine("/// <auto-generated />").AppendLine();

if (!string.IsNullOrEmpty(enumToProcess.FullNamespace))
{
builder.Append("namespace ").Append(enumToProcess.FullNamespace!).AppendLine();
builder.OpenBrackets();
}

builder.AppendLineWithIdent("[System.Serializable]");
builder.AppendIdent().Append(methodVisibility).Append(" class ").AppendLine(enumToProcess.ClassName);
builder.OpenBrackets();

foreach (var member in enumToProcess.Members)
{
builder
.AppendIdent().Append("[UnityEngine.SerializeField] public ")
.Append(typeName).Append(" ")
.Append(member.Name).AppendLine(";");
}

builder.AppendLine();
builder
.AppendIdent().Append("public ").Append(typeName)
.Append(" Get(").Append(enumToProcess.FullCsharpName).AppendLine(" key)");
builder.OpenBrackets();
builder.AppendLineWithIdent("return key switch");
builder.OpenBrackets();
foreach (var member in enumToProcess.Members)
{
builder.AppendIdent().Append(enumToProcess.FullCsharpName).Append(".").Append(member.Name)
.Append(" => ").Append(member.Name).AppendLine(",");
}
builder.AppendLineWithIdent("_ => throw new System.ArgumentOutOfRangeException(nameof(key), key, null),");
builder.DecreaseIdent().AppendLineWithIdent("};");
builder.CloseBrackets();

builder.CloseBrackets();

if (!string.IsNullOrEmpty(enumToProcess.FullNamespace))
{
builder.CloseBrackets();
}

return builder.ToString(); }
}
2 changes: 1 addition & 1 deletion UnityAttributes/UnityAttributes.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Version>1.3.1</Version>
<Version>1.4.0</Version>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>UnityAttributes</RootNamespace>
<LangVersion>latest</LangVersion>
Expand Down

0 comments on commit d6aea2c

Please sign in to comment.