forked from genaray/Arch.Extended
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSourceGenerator.cs
More file actions
142 lines (123 loc) · 6.37 KB
/
Copy pathSourceGenerator.cs
File metadata and controls
142 lines (123 loc) · 6.37 KB
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
using System.Collections.Immutable;
using System.Diagnostics;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace Arch.System.SourceGenerator;
/// <summary>
/// Generates queries.
/// </summary>
[Generator]
public class QueryGenerator : IIncrementalGenerator
{
/// <inheritdoc cref="IIncrementalGenerator.Initialize"/>
public void Initialize(IncrementalGeneratorInitializationContext context)
{
//if (!Debugger.IsAttached) Debugger.Launch();
// Do a simple filter for methods marked with update
IncrementalValuesProvider<MethodDeclarationSyntax> methodDeclarations = context.SyntaxProvider.CreateSyntaxProvider(
static (s, _) => s is MethodDeclarationSyntax { AttributeLists.Count: > 0 },
static (ctx, _) => GetMethodSymbolIfAttributeof(ctx, "Arch.System.QueryAttribute")
).Where(static m => m is not null)!; // filter out attributed methods that we don't care about
// Combine the selected enums with the `Compilation`
IncrementalValueProvider<(Compilation, ImmutableArray<MethodDeclarationSyntax>)> compilationAndMethods = context.CompilationProvider.Combine(methodDeclarations.WithComparer(Comparer.Instance).Collect());
context.RegisterSourceOutput(compilationAndMethods, static (spc, source) => Generate(source.Item1, source.Item2, spc));
}
/// <summary>
/// Adds a <see cref="IMethodSymbol"/> to its class.
/// </summary>
/// <param name="classToMethods">The dictionary mapping classes to their methods.</param>
/// <param name="methodSymbol">The <see cref="IMethodSymbol"/> which will be added/mapped to its class.</param>
private static void AddMethodToClass(Dictionary<ISymbol, List<IMethodSymbol>> classToMethods, IMethodSymbol methodSymbol)
{
if (!classToMethods.TryGetValue(methodSymbol.ContainingSymbol, out var list))
{
list = new List<IMethodSymbol>();
classToMethods[methodSymbol.ContainingSymbol] = list;
}
list.Add(methodSymbol);
}
/// <summary>
/// Returns a <see cref="MethodDeclarationSyntax"/> if it's annotated with an attribute of <paramref name="name"/>.
/// </summary>
/// <param name="context">Its <see cref="GeneratorSyntaxContext"/>.</param>
/// <param name="name">The attributes name.</param>
/// <returns></returns>
private static MethodDeclarationSyntax? GetMethodSymbolIfAttributeof(GeneratorSyntaxContext context, string name)
{
// we know the node is a EnumDeclarationSyntax thanks to IsSyntaxTargetForGeneration
var enumDeclarationSyntax = (MethodDeclarationSyntax)context.Node;
// loop through all the attributes on the method
foreach (var attributeListSyntax in enumDeclarationSyntax.AttributeLists)
{
foreach (var attributeSyntax in attributeListSyntax.Attributes)
{
if (ModelExtensions.GetSymbolInfo(context.SemanticModel, attributeSyntax).Symbol is not IMethodSymbol attributeSymbol) continue;
var attributeContainingTypeSymbol = attributeSymbol.ContainingType;
var fullName = attributeContainingTypeSymbol.ToDisplayString();
// Is the attribute the [EnumExtensions] attribute?
if (fullName != name) continue;
return enumDeclarationSyntax;
}
}
// we didn't find the attribute we were looking for
return null;
}
/// <summary>
/// Generates queries and partial classes for the found marked methods.
/// </summary>
/// <param name="compilation">The <see cref="Compilation"/>.</param>
/// <param name="methods">The <see cref="ImmutableArray{MethodDeclarationSyntax}"/> array, the methods which we will generate queries and classes for.</param>
/// <param name="context">The <see cref="SourceProductionContext"/>.</param>
private static void Generate(Compilation compilation, ImmutableArray<MethodDeclarationSyntax> methods, SourceProductionContext context)
{
if (methods.IsDefaultOrEmpty) return;
// Generate Query methods and map them to their classes
var classToMethods = new Dictionary<ISymbol, List<IMethodSymbol>>(512, SymbolEqualityComparer.Default);
foreach (var methodSyntax in methods)
{
IMethodSymbol? methodSymbol = null;
try
{
var semanticModel = compilation.GetSemanticModel(methodSyntax.SyntaxTree);
methodSymbol = ModelExtensions.GetDeclaredSymbol(semanticModel, methodSyntax) as IMethodSymbol;
}
catch
{
//not update,skip
continue;
}
AddMethodToClass(classToMethods, methodSymbol!);
var sb = new StringBuilder();
var method = sb.AppendQueryMethod(methodSymbol!);
var fileName = methodSymbol!.ToDisplayString(SymbolDisplayFormat.CSharpShortErrorMessageFormat).Replace('<', '{').Replace('>', '}');
context.AddSource($"{fileName}.g.cs",CSharpSyntaxTree.ParseText(method.ToString()).GetRoot().NormalizeWhitespace().ToFullString());
}
// Creating class that calls the created methods after another.
foreach (var classToMethod in classToMethods)
{
var template = new StringBuilder().AppendBaseSystem(classToMethod).ToString();
if (string.IsNullOrEmpty(template)) continue;
var fileName = ((INamedTypeSymbol)classToMethod.Key).ToDisplayString(SymbolDisplayFormat.CSharpShortErrorMessageFormat).Replace('<', '{').Replace('>', '}');
context.AddSource($"{fileName}.g.cs",
CSharpSyntaxTree.ParseText(template).GetRoot().NormalizeWhitespace().ToFullString());
}
}
/// <summary>
/// Compares <see cref="MethodDeclarationSyntax"/>s to remove duplicates.
/// </summary>
class Comparer : IEqualityComparer<MethodDeclarationSyntax>
{
public static readonly Comparer Instance = new();
public bool Equals(MethodDeclarationSyntax x, MethodDeclarationSyntax y)
{
return x.Equals(y);
}
public int GetHashCode(MethodDeclarationSyntax obj)
{
return obj.GetHashCode();
}
}
}