Skip to content

Commit 5df9a66

Browse files
committed
Generate factory using syntax tree API.
1 parent bc4e19e commit 5df9a66

File tree

4 files changed

+122
-16
lines changed

4 files changed

+122
-16
lines changed

src/UnityUxmlGenerator/Usings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
global using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.CSharp;
3+
using Microsoft.CodeAnalysis.CSharp.Syntax;
4+
5+
namespace UnityUxmlGenerator;
6+
7+
internal sealed partial class UxmlGenerator
8+
{
9+
private static CompilationUnitSyntax GetCompilationUnit(TypeDeclarationSyntax typeDeclarationSyntax,
10+
IReadOnlyList<MemberDeclarationSyntax>? memberDeclarations = null, string? @namespace = null,
11+
BaseListSyntax? baseList = null)
12+
{
13+
if (memberDeclarations is not null)
14+
{
15+
typeDeclarationSyntax = typeDeclarationSyntax.AddMembers(ProcessMemberDeclarations(memberDeclarations));
16+
}
17+
18+
if (baseList is not null)
19+
{
20+
typeDeclarationSyntax = typeDeclarationSyntax.WithBaseList(baseList);
21+
}
22+
23+
if (string.IsNullOrEmpty(@namespace))
24+
{
25+
// If there is no namespace, attach the pragma directly to the declared type,
26+
// and skip the namespace declaration. This will produce code as follows:
27+
//
28+
// <SYNTAX_TRIVIA>
29+
// <TYPE_HIERARCHY>
30+
return
31+
CompilationUnit()
32+
.AddMembers(typeDeclarationSyntax)
33+
.NormalizeWhitespace();
34+
}
35+
36+
// Prepare the leading trivia for the generated compilation unit.
37+
// This will produce code as follows:
38+
//
39+
// <auto-generated/>
40+
// #pragma warning disable
41+
// #nullable enable
42+
SyntaxTriviaList syntaxTriviaList = TriviaList(
43+
Comment("// <auto-generated/>"),
44+
Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.DisableKeyword), true)),
45+
Trivia(NullableDirectiveTrivia(Token(SyntaxKind.EnableKeyword), true)));
46+
47+
// Create the compilation unit with disabled warnings, target namespace and generated type.
48+
// This will produce code as follows:
49+
//
50+
// <SYNTAX_TRIVIA>
51+
// namespace <NAMESPACE>
52+
// {
53+
// <TYPE_HIERARCHY>
54+
// }
55+
return
56+
CompilationUnit()
57+
.AddMembers(NamespaceDeclaration(IdentifierName(@namespace!))
58+
.WithLeadingTrivia(syntaxTriviaList)
59+
.AddMembers(typeDeclarationSyntax))
60+
.NormalizeWhitespace();
61+
}
62+
63+
private static MemberDeclarationSyntax[] ProcessMemberDeclarations(
64+
IReadOnlyList<MemberDeclarationSyntax> memberDeclarations)
65+
{
66+
var annotatedMemberDeclarations = new MemberDeclarationSyntax[memberDeclarations.Count];
67+
68+
for (var i = 0; i < memberDeclarations.Count; i++)
69+
{
70+
annotatedMemberDeclarations[i] = ProcessMemberDeclaration(memberDeclarations[i]);
71+
}
72+
73+
return annotatedMemberDeclarations;
74+
}
75+
76+
private static MemberDeclarationSyntax ProcessMemberDeclaration(MemberDeclarationSyntax member)
77+
{
78+
// [GeneratedCode] is always present.
79+
member = member
80+
.WithoutLeadingTrivia()
81+
.AddAttributeLists(AttributeList(SingletonSeparatedList(
82+
Attribute(IdentifierName("global::System.CodeDom.Compiler.GeneratedCode"))
83+
.AddArgumentListArguments(
84+
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(AssemblyName.Name))),
85+
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(AssemblyName.Version.ToString())))))))
86+
.WithLeadingTrivia(member.GetLeadingTrivia());
87+
88+
// [ExcludeFromCodeCoverage] is not supported on interfaces and fields.
89+
if (member.Kind() is not SyntaxKind.InterfaceDeclaration and not SyntaxKind.FieldDeclaration)
90+
{
91+
member = member
92+
.AddAttributeLists(AttributeList(SingletonSeparatedList(
93+
Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage")))));
94+
}
95+
96+
return member;
97+
}
98+
}
Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,34 @@
1-
using UnityUxmlGenerator.Captures;
1+
using System.Text;
2+
using Microsoft.CodeAnalysis.CSharp;
3+
using Microsoft.CodeAnalysis.CSharp.Syntax;
4+
using Microsoft.CodeAnalysis.Text;
5+
using UnityUxmlGenerator.Captures;
26

37
namespace UnityUxmlGenerator;
48

59
internal sealed partial class UxmlGenerator
610
{
7-
private static string GenerateUxmlFactory(UxmlFactoryCapture capture)
11+
private static SourceText GenerateUxmlFactory(UxmlFactoryCapture capture)
812
{
9-
return $$"""
10-
// <auto-generated/>
11-
#pragma warning disable
13+
var @class = ClassDeclaration(capture.ClassName)
14+
.AddModifiers(Token(SyntaxKind.PartialKeyword));
1215

13-
#nullable enable
16+
var classMembers = GetFactoryClassMembers(capture);
1417

15-
namespace {{capture.ClassNamespace}}
16-
{
17-
partial class {{capture.ClassName}}
18-
{
19-
{{GeneratedCodeAttribute}}
20-
public new class UxmlFactory : global::UnityEngine.UIElements.UxmlFactory<{{capture.ClassName}}, UxmlTraits>
21-
{
22-
}
18+
return GetCompilationUnit(@class, classMembers, capture.ClassNamespace).GetText(Encoding.UTF8);
2319
}
24-
}
25-
""";
20+
21+
private static List<MemberDeclarationSyntax> GetFactoryClassMembers(UxmlFactoryCapture capture)
22+
{
23+
var uxmlFactoryBaseList =
24+
SimpleBaseType(
25+
IdentifierName($"global::UnityEngine.UIElements.UxmlFactory<{capture.ClassName}, UxmlTraits>"));
26+
27+
var uxmlFactoryClass =
28+
ClassDeclaration("UxmlFactory")
29+
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.NewKeyword)))
30+
.WithBaseList(BaseList(SingletonSeparatedList<BaseTypeSyntax>(uxmlFactoryBaseList)));
31+
32+
return new List<MemberDeclarationSyntax> { uxmlFactoryClass };
2633
}
2734
}

0 commit comments

Comments
 (0)