Skip to content

Commit 6dbde89

Browse files
committed
Generate traits using syntax tree API.
1 parent 5df9a66 commit 6dbde89

File tree

8 files changed

+140
-69
lines changed

8 files changed

+140
-69
lines changed

src/UnityUxmlGenerator/Captures/UxmlTraitsCapture.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public UxmlTraitsCapture(ClassDeclarationSyntax @class, TypeSyntax baseClassType
1212
ClassNamespace = @class.GetParent<NamespaceDeclarationSyntax>()!.Name.ToString();
1313

1414
BaseClassType = baseClassType;
15-
Properties = new List<(string PropertyName, string? DefaultValue)>();
15+
Properties = new List<(PropertyDeclarationSyntax property, string? DefaultValue)>();
1616
}
1717

1818
public string ClassName { get; }
@@ -21,7 +21,7 @@ public UxmlTraitsCapture(ClassDeclarationSyntax @class, TypeSyntax baseClassType
2121
public TypeSyntax BaseClassType { get; }
2222
public ClassDeclarationSyntax Class { get; }
2323

24-
public List<(string PropertyName, string? DefaultValue)> Properties { get; }
24+
public List<(PropertyDeclarationSyntax property, string? DefaultValue)> Properties { get; }
2525

2626
public string GetBaseClassName(out TypeSyntax? genericTypeSyntax)
2727
{
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using Microsoft.CodeAnalysis.CSharp.Syntax;
2+
3+
namespace UnityUxmlGenerator.Extensions;
4+
5+
internal static class PropertySyntaxExtensions
6+
{
7+
public static string GetName(this PropertyDeclarationSyntax property)
8+
{
9+
return property.Identifier.Text;
10+
}
11+
}

src/UnityUxmlGenerator/SyntaxReceivers/UxmlTraitsReceiver.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
3838
_captures.Add(@class.Identifier.Text, uxmlTraits);
3939
}
4040

41-
uxmlTraits.Properties.Add((property!.Identifier.Text, GetAttributeArgumentValue(attribute)));
41+
uxmlTraits.Properties.Add((property!, GetAttributeArgumentValue(attribute)));
4242
}
4343

4444
private static string? GetAttributeArgumentValue(AttributeSyntax attribute)

src/UnityUxmlGenerator/UxmlGenerator.Execute.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,13 @@ namespace UnityUxmlGenerator;
77
internal sealed partial class UxmlGenerator
88
{
99
private static CompilationUnitSyntax GetCompilationUnit(TypeDeclarationSyntax typeDeclarationSyntax,
10-
IReadOnlyList<MemberDeclarationSyntax>? memberDeclarations = null, string? @namespace = null,
11-
BaseListSyntax? baseList = null)
10+
string? @namespace = null, params MemberDeclarationSyntax[] memberDeclarations)
1211
{
13-
if (memberDeclarations is not null)
12+
if (memberDeclarations.Length != 0)
1413
{
1514
typeDeclarationSyntax = typeDeclarationSyntax.AddMembers(ProcessMemberDeclarations(memberDeclarations));
1615
}
1716

18-
if (baseList is not null)
19-
{
20-
typeDeclarationSyntax = typeDeclarationSyntax.WithBaseList(baseList);
21-
}
22-
2317
if (string.IsNullOrEmpty(@namespace))
2418
{
2519
// If there is no namespace, attach the pragma directly to the declared type,

src/UnityUxmlGenerator/UxmlGenerator.Factory.cs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,22 @@ internal sealed partial class UxmlGenerator
1010
{
1111
private static SourceText GenerateUxmlFactory(UxmlFactoryCapture capture)
1212
{
13-
var @class = ClassDeclaration(capture.ClassName)
14-
.AddModifiers(Token(SyntaxKind.PartialKeyword));
13+
var @class = ClassDeclaration(capture.ClassName).AddModifiers(Token(SyntaxKind.PartialKeyword));
1514

16-
var classMembers = GetFactoryClassMembers(capture);
15+
var factoryClass = GetFactoryClass(capture);
1716

18-
return GetCompilationUnit(@class, classMembers, capture.ClassNamespace).GetText(Encoding.UTF8);
17+
return GetCompilationUnit(@class, capture.ClassNamespace, factoryClass).GetText(Encoding.UTF8);
1918
}
2019

21-
private static List<MemberDeclarationSyntax> GetFactoryClassMembers(UxmlFactoryCapture capture)
20+
private static MemberDeclarationSyntax GetFactoryClass(UxmlFactoryCapture capture)
2221
{
2322
var uxmlFactoryBaseList =
2423
SimpleBaseType(
2524
IdentifierName($"global::UnityEngine.UIElements.UxmlFactory<{capture.ClassName}, UxmlTraits>"));
2625

27-
var uxmlFactoryClass =
26+
return
2827
ClassDeclaration("UxmlFactory")
2928
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.NewKeyword)))
3029
.WithBaseList(BaseList(SingletonSeparatedList<BaseTypeSyntax>(uxmlFactoryBaseList)));
31-
32-
return new List<MemberDeclarationSyntax> { uxmlFactoryClass };
3330
}
3431
}
Lines changed: 119 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,33 @@
11
using System.Text;
22
using Microsoft.CodeAnalysis;
3+
using Microsoft.CodeAnalysis.CSharp;
34
using Microsoft.CodeAnalysis.CSharp.Syntax;
5+
using Microsoft.CodeAnalysis.Text;
46
using UnityUxmlGenerator.Captures;
57
using UnityUxmlGenerator.Extensions;
68

79
namespace UnityUxmlGenerator;
810

911
internal sealed partial class UxmlGenerator
1012
{
11-
private static string GenerateUxmlTraits(GeneratorExecutionContext context, UxmlTraitsCapture capture)
13+
private static SourceText GenerateUxmlTraits(GeneratorExecutionContext context, UxmlTraitsCapture capture)
1214
{
13-
return $$"""
14-
// <auto-generated/>
15-
#pragma warning disable
15+
var @class = ClassDeclaration(capture.ClassName).AddModifiers(Token(SyntaxKind.PartialKeyword));
1616

17-
#nullable enable
17+
var traitsClass = GetTraitsClass(context, capture);
1818

19-
namespace {{capture.ClassNamespace}}
20-
{
21-
partial class {{capture.ClassName}}
19+
return GetCompilationUnit(@class, capture.ClassNamespace, traitsClass).GetText(Encoding.UTF8);
20+
}
21+
22+
private static MemberDeclarationSyntax GetTraitsClass(GeneratorExecutionContext context, UxmlTraitsCapture capture)
2223
{
23-
{{GeneratedCodeAttribute}}
24-
public new class UxmlTraits : {{GetBaseClassName(context, capture)}}.UxmlTraits
25-
{
26-
{{GetUxmlTraitsFields(capture.Properties)}}
24+
var uxmlTraitsBaseList = SimpleBaseType(IdentifierName($"{GetBaseClassName(context, capture)}.UxmlTraits"));
2725

28-
{{GetUxmlTraitsInitialization(capture.ClassName, capture.Properties)}}
29-
}
30-
}
31-
}
32-
""";
26+
return
27+
ClassDeclaration("UxmlTraits")
28+
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.NewKeyword)))
29+
.WithBaseList(BaseList(SingletonSeparatedList<BaseTypeSyntax>(uxmlTraitsBaseList)))
30+
.WithMembers(List(GetTraitsClassMembers(capture)));
3331
}
3432

3533
private static string GetBaseClassName(GeneratorExecutionContext context, UxmlTraitsCapture capture)
@@ -58,52 +56,125 @@ private static string GetBaseClassName(GeneratorExecutionContext context, UxmlTr
5856
return string.Empty;
5957
}
6058

61-
private static string GetUxmlTraitsFields(List<(string, string?)> properties)
59+
private static IEnumerable<MemberDeclarationSyntax> GetTraitsClassMembers(UxmlTraitsCapture capture)
6260
{
63-
var stringBuilder = new StringBuilder();
61+
var members = new List<MemberDeclarationSyntax>(GetAttributeFields(capture));
6462

65-
foreach (var (propertyName, uxmlAttributeDefaultValue) in properties)
63+
var initMethodBody = new List<StatementSyntax>
6664
{
65+
ExpressionStatement(
66+
InvocationExpression(MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, BaseExpression(),
67+
IdentifierName("Init")))
68+
.WithArgumentList(ArgumentList(SeparatedList<ArgumentSyntax>(new SyntaxNodeOrToken[]
69+
{
70+
Argument(IdentifierName("visualElement")),
71+
Token(SyntaxKind.CommaToken),
72+
Argument(IdentifierName("bag")),
73+
Token(SyntaxKind.CommaToken),
74+
Argument(IdentifierName("context"))
75+
})))),
76+
LocalDeclarationStatement(
77+
VariableDeclaration(IdentifierName(Identifier(TriviaList(), SyntaxKind.VarKeyword, "var", "var", TriviaList())))
78+
.WithVariables(SingletonSeparatedList(
79+
VariableDeclarator(Identifier("control"))
80+
.WithInitializer(EqualsValueClause(CastExpression(IdentifierName(capture.ClassName), IdentifierName("visualElement")))))))
81+
};
82+
83+
initMethodBody.AddRange(GetAttributeValueAssignments(capture));
84+
85+
var initMethod =
86+
MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)), Identifier("Init"))
87+
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.OverrideKeyword)))
88+
.WithParameterList(ParameterList(SeparatedList<ParameterSyntax>(new SyntaxNodeOrToken[]
89+
{
90+
Parameter(Identifier("visualElement"))
91+
.WithType(IdentifierName("global::UnityEngine.UIElements.VisualElement")),
92+
Token(SyntaxKind.CommaToken),
93+
Parameter(Identifier("bag"))
94+
.WithType(IdentifierName("global::UnityEngine.UIElements.IUxmlAttributes")),
95+
Token(SyntaxKind.CommaToken),
96+
Parameter(Identifier("context"))
97+
.WithType(IdentifierName("global::UnityEngine.UIElements.CreationContext"))
98+
})))
99+
.WithBody(Block(initMethodBody));
100+
101+
members.Add(initMethod);
102+
103+
return ProcessMemberDeclarations(members);
104+
}
105+
106+
private static IEnumerable<MemberDeclarationSyntax> GetAttributeFields(UxmlTraitsCapture capture)
107+
{
108+
var fields = new List<MemberDeclarationSyntax>();
109+
110+
foreach (var (property, uxmlAttributeDefaultValue) in capture.Properties)
111+
{
112+
var propertyName = property.GetName();
113+
114+
var fieldName = propertyName.ToPrivateFieldName();
115+
116+
var attributeType = "UxmlStringAttributeDescription";
67117
var attributeUxmlName = propertyName.ToDashCase();
68-
var attributeFieldName = propertyName.ToPrivateFieldName();
69118
var attributeDefaultValue = uxmlAttributeDefaultValue ?? string.Empty;
70119

71-
stringBuilder.AppendLine($"\t\t\t{GeneratedCodeAttribute}");
72-
stringBuilder.AppendLine($$"""
73-
private readonly global::UnityEngine.UIElements.UxmlStringAttributeDescription {{attributeFieldName}} = new()
74-
{ name = "{{attributeUxmlName}}", defaultValue = "{{attributeDefaultValue}}" };
75-
""");
76-
stringBuilder.AppendLine();
120+
fields.Add(GetAttributeFieldDeclaration(attributeType, fieldName, attributeUxmlName,
121+
attributeDefaultValue));
77122
}
78123

79-
return stringBuilder.ToString().Trim();
124+
return fields;
80125
}
81126

82-
private static string GetUxmlTraitsInitialization(string elementClassName,
83-
List<(string, string?)> properties)
127+
private static FieldDeclarationSyntax GetAttributeFieldDeclaration(string attributeType, string fieldName,
128+
string attributeName, string attributeDefaultValue)
84129
{
85-
var stringBuilder = new StringBuilder();
130+
return
131+
FieldDeclaration(VariableDeclaration(IdentifierName($"global::UnityEngine.UIElements.{attributeType}"))
132+
.WithVariables(SingletonSeparatedList(VariableDeclarator(Identifier(fieldName))
133+
.WithInitializer(EqualsValueClause(ImplicitObjectCreationExpression()
134+
.WithInitializer(InitializerExpression(SyntaxKind.ObjectInitializerExpression,
135+
SeparatedList<ExpressionSyntax>(new SyntaxNodeOrToken[]
136+
{
137+
AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
138+
IdentifierName("name"),
139+
LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(attributeName))),
140+
Token(SyntaxKind.CommaToken),
141+
AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
142+
IdentifierName("defaultValue"),
143+
LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(attributeDefaultValue)))
144+
}))))))))
145+
.WithModifiers(TokenList(Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.ReadOnlyKeyword)));
146+
}
86147

87-
foreach (var (propertyName, _) in properties)
148+
private static IEnumerable<StatementSyntax> GetAttributeValueAssignments(UxmlTraitsCapture capture)
149+
{
150+
var attributeValueAssignments = new List<StatementSyntax>();
151+
152+
foreach (var (property, _) in capture.Properties)
88153
{
89-
var attributeFieldName = propertyName.ToPrivateFieldName();
154+
var propertyName = property.GetName();
155+
var fieldName = propertyName.ToPrivateFieldName();
90156

91-
stringBuilder.AppendLine(
92-
$"\t\t\t\tcontrol.{propertyName} = {attributeFieldName}.GetValueFromBag(bag, context);");
157+
attributeValueAssignments.Add(GetAttributeValueAssignment(propertyName, fieldName));
93158
}
94159

95-
return $$"""
96-
{{GeneratedCodeAttribute}}
97-
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
98-
public override void Init(global::UnityEngine.UIElements.VisualElement visualElement,
99-
global::UnityEngine.UIElements.IUxmlAttributes bag,
100-
global::UnityEngine.UIElements.CreationContext context)
101-
{
102-
base.Init(visualElement, bag, context);
103-
104-
var control = ({{elementClassName}}) visualElement;
105-
{{stringBuilder.ToString().Trim()}}
106-
}
107-
""".Trim();
160+
return attributeValueAssignments;
161+
}
162+
163+
private static StatementSyntax GetAttributeValueAssignment(string propertyName, string fieldName)
164+
{
165+
return
166+
ExpressionStatement(
167+
AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
168+
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName("control"),
169+
IdentifierName(propertyName)),
170+
InvocationExpression(MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
171+
IdentifierName(fieldName), IdentifierName("GetValueFromBag")))
172+
.WithArgumentList(ArgumentList(SeparatedList<ArgumentSyntax>(
173+
new SyntaxNodeOrToken[]
174+
{
175+
Argument(IdentifierName("bag")),
176+
Token(SyntaxKind.CommaToken),
177+
Argument(IdentifierName("context"))
178+
})))));
108179
}
109180
}

src/UnityUxmlGenerator/UxmlGenerator.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44

55
namespace UnityUxmlGenerator;
66

7-
// TODO: Use a syntax tree API to generate source code.
8-
97
[Generator]
108
internal sealed partial class UxmlGenerator : ISourceGenerator
119
{

0 commit comments

Comments
 (0)