-
Notifications
You must be signed in to change notification settings - Fork 507
/
Copy pathStaticViewLocatorGenerator.cs
152 lines (125 loc) · 4.91 KB
/
StaticViewLocatorGenerator.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace WalletWasabi.Fluent.Generators;
[Generator]
public class StaticViewLocatorGenerator : ISourceGenerator
{
private const string StaticViewLocatorAttributeDisplayString = "WalletWasabi.Fluent.StaticViewLocatorAttribute";
private const string ViewModelSuffix = "ViewModel";
private const string ViewSuffix = "View";
private const string AttributeText = @"// <auto-generated />
using System;
namespace WalletWasabi.Fluent
{
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class StaticViewLocatorAttribute : Attribute
{
}
}";
public void Initialize(GeneratorInitializationContext context)
{
// System.Diagnostics.Debugger.Launch();
context.RegisterForPostInitialization((i) => i.AddSource("StaticViewLocatorAttribute.cs", SourceText.From(AttributeText, Encoding.UTF8)));
context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
}
public void Execute(GeneratorExecutionContext context)
{
if (context.SyntaxContextReceiver is not SyntaxReceiver receiver)
{
return;
}
var attributeSymbol = context.Compilation.GetTypeByMetadataName(StaticViewLocatorAttributeDisplayString);
if (attributeSymbol is null)
{
return;
}
foreach (var namedTypeSymbol in receiver.NamedTypeSymbolLocators)
{
var namedTypeSymbolViewModels = receiver.NamedTypeSymbolViewModels.ToList();
namedTypeSymbolViewModels.Sort((x, y) => x.ToDisplayString().CompareTo(y.ToDisplayString()));
var classSource = ProcessClass(context.Compilation, namedTypeSymbol, namedTypeSymbolViewModels);
if (classSource is not null)
{
context.AddSource($"{namedTypeSymbol.Name}_StaticViewLocator.cs", SourceText.From(classSource, Encoding.UTF8));
}
}
}
private static string? ProcessClass(Compilation compilation, INamedTypeSymbol namedTypeSymbolLocator, List<INamedTypeSymbol> namedTypeSymbolViewModels)
{
if (!namedTypeSymbolLocator.ContainingSymbol.Equals(namedTypeSymbolLocator.ContainingNamespace, SymbolEqualityComparer.Default))
{
return null;
}
string namespaceNameLocator = namedTypeSymbolLocator.ContainingNamespace.ToDisplayString();
var format = new SymbolDisplayFormat(
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes,
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters | SymbolDisplayGenericsOptions.IncludeTypeConstraints | SymbolDisplayGenericsOptions.IncludeVariance);
string classNameLocator = namedTypeSymbolLocator.ToDisplayString(format);
var source = new StringBuilder($@"// <auto-generated />
#nullable enable
using System;
using System.Collections.Generic;
using Avalonia.Controls;
namespace {namespaceNameLocator}
{{
public partial class {classNameLocator}
{{");
source.Append($@"
private static Dictionary<Type, Func<Control>> s_views = new()
{{
");
var userControlViewSymbol = compilation.GetTypeByMetadataName("Avalonia.Controls.UserControl");
foreach (var namedTypeSymbolViewModel in namedTypeSymbolViewModels)
{
string namespaceNameViewModel = namedTypeSymbolViewModel.ContainingNamespace.ToDisplayString();
string classNameViewModel = $"{namespaceNameViewModel}.{namedTypeSymbolViewModel.ToDisplayString(format)}";
string classNameView = classNameViewModel.Replace(ViewModelSuffix, ViewSuffix);
var classNameViewSymbol = compilation.GetTypeByMetadataName(classNameView);
if (classNameViewSymbol is null || classNameViewSymbol.BaseType?.Equals(userControlViewSymbol, SymbolEqualityComparer.Default) != true)
{
source.AppendLine($@" [typeof({classNameViewModel})] = () => new TextBlock() {{ Text = {("\"Not Found: " + classNameView + "\"")} }},");
}
else
{
source.AppendLine($@" [typeof({classNameViewModel})] = () => new {classNameView}(),");
}
}
source.Append($@" }};
}}
}}");
return source.ToString();
}
private class SyntaxReceiver : ISyntaxContextReceiver
{
public List<INamedTypeSymbol> NamedTypeSymbolLocators { get; } = new();
public List<INamedTypeSymbol> NamedTypeSymbolViewModels { get; } = new();
public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
{
if (context.Node is ClassDeclarationSyntax classDeclarationSyntax)
{
var namedTypeSymbol = context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax);
if (namedTypeSymbol is null)
{
return;
}
var attributes = namedTypeSymbol.GetAttributes();
if (attributes.Any(ad => ad?.AttributeClass?.ToDisplayString() == StaticViewLocatorAttributeDisplayString))
{
NamedTypeSymbolLocators.Add(namedTypeSymbol);
}
else if (namedTypeSymbol.Name.EndsWith(ViewModelSuffix))
{
if (!namedTypeSymbol.IsAbstract)
{
NamedTypeSymbolViewModels.Add(namedTypeSymbol);
}
}
}
}
}
}