Skip to content

Commit 7e8a058

Browse files
committed
Add all uxml attributes support.
1 parent 6dbde89 commit 7e8a058

File tree

7 files changed

+249
-29
lines changed

7 files changed

+249
-29
lines changed

src/UnityUxmlGenerator/Extensions/StringExtensions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ namespace UnityUxmlGenerator.Extensions;
44

55
internal static class StringExtensions
66
{
7+
public static string FirstCharToUpper(this string str)
8+
{
9+
return $"{char.ToUpper(str[0])}{str.Substring(1)}";
10+
}
11+
712
public static string ToPrivateFieldName(this string propertyName)
813
{
914
return string.Concat("_", char.ToLower(propertyName[0]), propertyName.Substring(1));
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using Microsoft.CodeAnalysis.CSharp;
2+
using Microsoft.CodeAnalysis.CSharp.Syntax;
3+
4+
namespace UnityUxmlGenerator.Extensions;
5+
6+
internal static class TypeSyntaxExtensions
7+
{
8+
public static bool IsBoolType(this TypeSyntax typeSyntax)
9+
{
10+
if (typeSyntax is PredefinedTypeSyntax predefinedTypeSyntax)
11+
{
12+
return IsBoolType(predefinedTypeSyntax);
13+
}
14+
15+
return IsBoolKind(typeSyntax.RawKind);
16+
}
17+
18+
public static bool IsBoolType(this PredefinedTypeSyntax typeSyntax)
19+
{
20+
return IsBoolKind(typeSyntax.Keyword.RawKind);
21+
}
22+
23+
public static bool IsStringType(this TypeSyntax typeSyntax)
24+
{
25+
if (typeSyntax is PredefinedTypeSyntax predefinedTypeSyntax)
26+
{
27+
return IsStringType(predefinedTypeSyntax);
28+
}
29+
30+
return IsStringKind(typeSyntax.RawKind);
31+
}
32+
33+
public static bool IsStringType(this PredefinedTypeSyntax typeSyntax)
34+
{
35+
return IsStringKind(typeSyntax.Keyword.RawKind);
36+
}
37+
38+
public static bool IsNumericType(this TypeSyntax typeSyntax)
39+
{
40+
if (typeSyntax is PredefinedTypeSyntax predefinedTypeSyntax)
41+
{
42+
return IsNumericType(predefinedTypeSyntax);
43+
}
44+
45+
return IsNumericKind(typeSyntax.RawKind);
46+
}
47+
48+
public static bool IsNumericType(this PredefinedTypeSyntax typeSyntax)
49+
{
50+
return IsNumericKind(typeSyntax.Keyword.RawKind);
51+
}
52+
53+
private static bool IsBoolKind(int rawKind)
54+
{
55+
return rawKind == (int) SyntaxKind.BoolKeyword;
56+
}
57+
58+
private static bool IsStringKind(int rawKind)
59+
{
60+
return rawKind == (int) SyntaxKind.StringKeyword;
61+
}
62+
63+
private static bool IsNumericKind(int rawKind)
64+
{
65+
return rawKind == (int) SyntaxKind.IntKeyword ||
66+
rawKind == (int) SyntaxKind.LongKeyword ||
67+
rawKind == (int) SyntaxKind.FloatKeyword ||
68+
rawKind == (int) SyntaxKind.DoubleKeyword;
69+
}
70+
}
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.Structs;
4+
5+
public ref struct UxmlAttributeInfo
6+
{
7+
public string TypeIdentifier { get; set; }
8+
public string PrivateFieldName { get; set; }
9+
public string AttributeUxmlName { get; set; }
10+
public ExpressionSyntax DefaultValueAssignmentExpression { get; set; }
11+
}

src/UnityUxmlGenerator/SyntaxReceivers/UxmlTraitsReceiver.cs

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.CSharp;
23
using Microsoft.CodeAnalysis.CSharp.Syntax;
34
using UnityUxmlGenerator.Captures;
45
using UnityUxmlGenerator.Extensions;
@@ -24,9 +25,20 @@ public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
2425
}
2526

2627
var property = attribute.GetParent<PropertyDeclarationSyntax>();
28+
if (property is null)
29+
{
30+
return;
31+
}
2732

28-
var @class = property?.GetParent<ClassDeclarationSyntax>();
33+
if (attribute.ArgumentList is not null && attribute.ArgumentList.Arguments.Any())
34+
{
35+
if (HasSameType(property, attribute) == false)
36+
{
37+
return;
38+
}
39+
}
2940

41+
var @class = property.GetParent<ClassDeclarationSyntax>();
3042
if (@class?.BaseList is null || @class.BaseList.Types.Count == 0)
3143
{
3244
return;
@@ -38,15 +50,57 @@ public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
3850
_captures.Add(@class.Identifier.Text, uxmlTraits);
3951
}
4052

41-
uxmlTraits.Properties.Add((property!, GetAttributeArgumentValue(attribute)));
53+
uxmlTraits.Properties.Add((property, GetAttributeArgumentValue(attribute)));
54+
}
55+
56+
private static bool HasSameType(BasePropertyDeclarationSyntax property, AttributeSyntax attribute)
57+
{
58+
var parameter = attribute.ArgumentList!.Arguments.First().Expression;
59+
60+
if (property.Type is PredefinedTypeSyntax predefinedType)
61+
{
62+
if (predefinedType.IsBoolType() &&
63+
(parameter.IsKind(SyntaxKind.TrueLiteralExpression) ||
64+
parameter.IsKind(SyntaxKind.FalseLiteralExpression)))
65+
{
66+
return true;
67+
}
68+
69+
if (predefinedType.IsStringType() &&
70+
parameter.IsKind(SyntaxKind.StringLiteralExpression))
71+
{
72+
return true;
73+
}
74+
75+
if (predefinedType.IsNumericType() &&
76+
parameter.IsKind(SyntaxKind.NumericLiteralExpression))
77+
{
78+
return true;
79+
}
80+
}
81+
82+
if (property.Type is IdentifierNameSyntax identifierName)
83+
{
84+
if (identifierName.Identifier.IsKind(SyntaxKind.IdentifierToken) &&
85+
(parameter.IsKind(SyntaxKind.InvocationExpression) ||
86+
parameter.IsKind(SyntaxKind.SimpleMemberAccessExpression)))
87+
{
88+
return true;
89+
}
90+
}
91+
92+
return false;
4293
}
4394

4495
private static string? GetAttributeArgumentValue(AttributeSyntax attribute)
4596
{
4697
return attribute.ArgumentList?.Arguments.Single().Expression switch
4798
{
48-
LiteralExpressionSyntax literal => literal.Token.ValueText,
99+
LiteralExpressionSyntax literal => literal.Token.IsKind(SyntaxKind.StringLiteralToken)
100+
? literal.Token.ValueText
101+
: literal.Token.Text,
49102
InvocationExpressionSyntax invocation => invocation.ArgumentList.Arguments.Single().Expression.GetText().ToString(),
103+
MemberAccessExpressionSyntax member => member.Parent?.ToString(),
50104
_ => null
51105
};
52106
}

src/UnityUxmlGenerator/UxmlGenerator.Attributes.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@ namespace {{AssemblyName.Name}}
3737
[global::System.AttributeUsageAttribute(global::System.AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
3838
internal sealed class UxmlAttributeAttribute : global::System.Attribute
3939
{
40-
public UxmlAttributeAttribute(string defaultValue = "")
40+
public UxmlAttributeAttribute(object? defaultValue = default)
4141
{
42-
DefaultValue = defaultValue ?? string.Empty;
42+
DefaultValue = defaultValue;
4343
}
4444
45-
public string DefaultValue { get; }
45+
public object? DefaultValue { get; }
4646
}
4747
}
4848
""";

src/UnityUxmlGenerator/UxmlGenerator.Traits.cs

Lines changed: 103 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,16 @@
55
using Microsoft.CodeAnalysis.Text;
66
using UnityUxmlGenerator.Captures;
77
using UnityUxmlGenerator.Extensions;
8+
using UnityUxmlGenerator.Structs;
89

910
namespace UnityUxmlGenerator;
1011

1112
internal sealed partial class UxmlGenerator
1213
{
14+
private const string UnityColorTypeFullName = "global::UnityEngine.Color";
15+
private const string UnityUiElementsFullName = "global::UnityEngine.UIElements.{0}";
16+
private const string UxmlColorAttributeDescription = "UxmlColorAttributeDescription";
17+
1318
private static SourceText GenerateUxmlTraits(GeneratorExecutionContext context, UxmlTraitsCapture capture)
1419
{
1520
var @class = ClassDeclaration(capture.ClassName).AddModifiers(Token(SyntaxKind.PartialKeyword));
@@ -27,7 +32,7 @@ private static MemberDeclarationSyntax GetTraitsClass(GeneratorExecutionContext
2732
ClassDeclaration("UxmlTraits")
2833
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.NewKeyword)))
2934
.WithBaseList(BaseList(SingletonSeparatedList<BaseTypeSyntax>(uxmlTraitsBaseList)))
30-
.WithMembers(List(GetTraitsClassMembers(capture)));
35+
.WithMembers(List(GetTraitsClassMembers(context, capture)));
3136
}
3237

3338
private static string GetBaseClassName(GeneratorExecutionContext context, UxmlTraitsCapture capture)
@@ -56,9 +61,10 @@ private static string GetBaseClassName(GeneratorExecutionContext context, UxmlTr
5661
return string.Empty;
5762
}
5863

59-
private static IEnumerable<MemberDeclarationSyntax> GetTraitsClassMembers(UxmlTraitsCapture capture)
64+
private static IEnumerable<MemberDeclarationSyntax> GetTraitsClassMembers(GeneratorExecutionContext context,
65+
UxmlTraitsCapture capture)
6066
{
61-
var members = new List<MemberDeclarationSyntax>(GetAttributeFields(capture));
67+
var members = new List<MemberDeclarationSyntax>(GetAttributeFields(context, capture));
6268

6369
var initMethodBody = new List<StatementSyntax>
6470
{
@@ -88,13 +94,13 @@ private static IEnumerable<MemberDeclarationSyntax> GetTraitsClassMembers(UxmlTr
8894
.WithParameterList(ParameterList(SeparatedList<ParameterSyntax>(new SyntaxNodeOrToken[]
8995
{
9096
Parameter(Identifier("visualElement"))
91-
.WithType(IdentifierName("global::UnityEngine.UIElements.VisualElement")),
97+
.WithType(IdentifierName(string.Format(UnityUiElementsFullName, "VisualElement"))),
9298
Token(SyntaxKind.CommaToken),
9399
Parameter(Identifier("bag"))
94-
.WithType(IdentifierName("global::UnityEngine.UIElements.IUxmlAttributes")),
100+
.WithType(IdentifierName(string.Format(UnityUiElementsFullName, "IUxmlAttributes"))),
95101
Token(SyntaxKind.CommaToken),
96102
Parameter(Identifier("context"))
97-
.WithType(IdentifierName("global::UnityEngine.UIElements.CreationContext"))
103+
.WithType(IdentifierName(string.Format(UnityUiElementsFullName, "CreationContext")))
98104
})))
99105
.WithBody(Block(initMethodBody));
100106

@@ -103,44 +109,118 @@ private static IEnumerable<MemberDeclarationSyntax> GetTraitsClassMembers(UxmlTr
103109
return ProcessMemberDeclarations(members);
104110
}
105111

106-
private static IEnumerable<MemberDeclarationSyntax> GetAttributeFields(UxmlTraitsCapture capture)
112+
private static IEnumerable<MemberDeclarationSyntax> GetAttributeFields(GeneratorExecutionContext context,
113+
UxmlTraitsCapture capture)
107114
{
108115
var fields = new List<MemberDeclarationSyntax>();
109116

110117
foreach (var (property, uxmlAttributeDefaultValue) in capture.Properties)
111118
{
112-
var propertyName = property.GetName();
119+
fields.Add(GetAttributeFieldDeclaration(GetAttributeInfo(context, property, uxmlAttributeDefaultValue)));
120+
}
113121

114-
var fieldName = propertyName.ToPrivateFieldName();
122+
return fields;
123+
}
115124

116-
var attributeType = "UxmlStringAttributeDescription";
117-
var attributeUxmlName = propertyName.ToDashCase();
118-
var attributeDefaultValue = uxmlAttributeDefaultValue ?? string.Empty;
125+
private static UxmlAttributeInfo GetAttributeInfo(GeneratorExecutionContext context,
126+
PropertyDeclarationSyntax property, string? uxmlAttributeDefaultValue)
127+
{
128+
var propertyName = property.GetName();
129+
130+
var info = new UxmlAttributeInfo
131+
{
132+
TypeIdentifier = GetPropertyTypeIdentifier(context, property, out var typeSyntax),
133+
PrivateFieldName = propertyName.ToPrivateFieldName(),
134+
AttributeUxmlName = propertyName.ToDashCase()
135+
};
119136

120-
fields.Add(GetAttributeFieldDeclaration(attributeType, fieldName, attributeUxmlName,
121-
attributeDefaultValue));
137+
if (uxmlAttributeDefaultValue is null || typeSyntax is null)
138+
{
139+
info.DefaultValueAssignmentExpression =
140+
LiteralExpression(SyntaxKind.DefaultLiteralExpression, Token(SyntaxKind.DefaultKeyword));
141+
return info;
122142
}
123143

124-
return fields;
144+
if (typeSyntax.IsBoolType())
145+
{
146+
info.DefaultValueAssignmentExpression = IdentifierName(uxmlAttributeDefaultValue);
147+
return info;
148+
}
149+
150+
if (typeSyntax.IsStringType())
151+
{
152+
info.DefaultValueAssignmentExpression = LiteralExpression(SyntaxKind.StringLiteralExpression,
153+
Literal(uxmlAttributeDefaultValue));
154+
return info;
155+
}
156+
157+
if (typeSyntax.IsNumericType())
158+
{
159+
info.DefaultValueAssignmentExpression = LiteralExpression(SyntaxKind.NumericLiteralExpression,
160+
Literal(uxmlAttributeDefaultValue, uxmlAttributeDefaultValue));
161+
return info;
162+
}
163+
164+
if (info.TypeIdentifier == UxmlColorAttributeDescription)
165+
{
166+
info.DefaultValueAssignmentExpression = IdentifierName($"global::UnityEngine.{uxmlAttributeDefaultValue}");
167+
return info;
168+
}
169+
170+
info.DefaultValueAssignmentExpression = IdentifierName(uxmlAttributeDefaultValue);
171+
return info;
172+
}
173+
174+
private static string GetPropertyTypeIdentifier(GeneratorExecutionContext context,
175+
BasePropertyDeclarationSyntax property, out TypeSyntax? typeSyntax)
176+
{
177+
switch (property.Type)
178+
{
179+
case PredefinedTypeSyntax predefinedType:
180+
{
181+
var propertyTypeIdentifier = predefinedType.Keyword.Text.FirstCharToUpper();
182+
183+
typeSyntax = predefinedType;
184+
185+
return $"Uxml{propertyTypeIdentifier}AttributeDescription";
186+
}
187+
188+
case IdentifierNameSyntax customTypeSyntax:
189+
{
190+
var type = customTypeSyntax.Identifier.Text;
191+
var typeNamespace = customTypeSyntax.GetTypeNamespace(context);
192+
var propertyTypeText = $"global::{typeNamespace}.{type}";
193+
194+
typeSyntax = customTypeSyntax;
195+
196+
return propertyTypeText == UnityColorTypeFullName
197+
? UxmlColorAttributeDescription
198+
: $"UxmlEnumAttributeDescription<{propertyTypeText}>";
199+
}
200+
201+
default:
202+
typeSyntax = default;
203+
return property.Type.GetText().ToString().Trim();
204+
}
125205
}
126206

127-
private static FieldDeclarationSyntax GetAttributeFieldDeclaration(string attributeType, string fieldName,
128-
string attributeName, string attributeDefaultValue)
207+
private static FieldDeclarationSyntax GetAttributeFieldDeclaration(UxmlAttributeInfo attributeInfo)
129208
{
130209
return
131-
FieldDeclaration(VariableDeclaration(IdentifierName($"global::UnityEngine.UIElements.{attributeType}"))
132-
.WithVariables(SingletonSeparatedList(VariableDeclarator(Identifier(fieldName))
210+
FieldDeclaration(VariableDeclaration(
211+
IdentifierName(string.Format(UnityUiElementsFullName, attributeInfo.TypeIdentifier)))
212+
.WithVariables(SingletonSeparatedList(VariableDeclarator(Identifier(attributeInfo.PrivateFieldName))
133213
.WithInitializer(EqualsValueClause(ImplicitObjectCreationExpression()
134214
.WithInitializer(InitializerExpression(SyntaxKind.ObjectInitializerExpression,
135215
SeparatedList<ExpressionSyntax>(new SyntaxNodeOrToken[]
136216
{
137217
AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
138-
IdentifierName("name"),
139-
LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(attributeName))),
218+
IdentifierName("name"),
219+
LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(attributeInfo.AttributeUxmlName))),
140220
Token(SyntaxKind.CommaToken),
141221
AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
142-
IdentifierName("defaultValue"),
143-
LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(attributeDefaultValue)))
222+
IdentifierName("defaultValue"),
223+
attributeInfo.DefaultValueAssignmentExpression)
144224
}))))))))
145225
.WithModifiers(TokenList(Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.ReadOnlyKeyword)));
146226
}

0 commit comments

Comments
 (0)