-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathSourceGenerator.cs
More file actions
150 lines (127 loc) · 5.93 KB
/
SourceGenerator.cs
File metadata and controls
150 lines (127 loc) · 5.93 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
143
144
145
146
147
148
149
150
using Microsoft.CodeAnalysis;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using static HandyIpc.Generator.DiagnosticDescriptors;
namespace HandyIpc.Generator
{
[Generator]
public class SourceGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
//System.Diagnostics.Debugger.Launch();
if (context.SyntaxReceiver is not SyntaxReceiver receiver)
{
return;
}
Compilation compilation = context.Compilation;
Extensions.Initialize(compilation);
INamedTypeSymbol? ipcContractAttributeSymbol = compilation.GetTypeByMetadataName("HandyIpc.IpcContractAttribute");
if (ipcContractAttributeSymbol is null)
{
context.ReportDiagnostic(Diagnostic.Create(HandyIpcNotReferenced, Location.None));
return;
}
var contractInterfaces = receiver.CandidateInterfaces
.GroupBy(@interface => @interface.SyntaxTree)
.SelectMany(group =>
{
SemanticModel model = compilation.GetSemanticModel(group.Key);
return group
.Select(@interface => model.GetDeclaredSymbol(@interface))
.Where(@interface => @interface is not null)
// WORKAROUND: The @interface must not be null here.
.Select(@interface => @interface!)
.Where(@interface => ContainsAttribute(@interface, ipcContractAttributeSymbol));
});
var fileNameCounter = new Dictionary<string, int>();
foreach (var @interface in contractInterfaces)
{
ISymbol[] members = @interface.GetMembers().ToArray();
IMethodSymbol[] methods = members.OfType<IMethodSymbol>()
.Where(item => item.MethodKind
is not MethodKind.EventAdd
and not MethodKind.EventRemove
and not MethodKind.EventRaise)
.ToArray();
IEventSymbol[] events = members.OfType<IEventSymbol>().ToArray();
if (members.Length != methods.Length + events.Length * 3)
{
foreach (Location location in @interface.Locations)
{
context.ReportDiagnostic(Diagnostic.Create(ContainsNotSupportedMembers, location, @interface.Name));
}
continue;
}
if (@interface.Interfaces.Length > 0)
{
foreach (Location location in @interface.Locations)
{
context.ReportDiagnostic(Diagnostic.Create(NoInheritance, location, @interface.Name));
}
continue;
}
if (!methods.Any() && !events.Any())
{
foreach (Location location in @interface.Locations)
{
context.ReportDiagnostic(Diagnostic.Create(MustContainsMethod, location, @interface.Name));
}
continue;
}
bool hasInvalidEvent = false;
foreach (var location in events.Where(@event => !@event.IsStdEventHandler()).SelectMany(@event => @event.Locations))
{
hasInvalidEvent = true;
context.ReportDiagnostic(Diagnostic.Create(UseStandardEventHandler, location, @interface.Name));
}
if (hasInvalidEvent)
{
continue;
}
string clientProxySource = ClientProxy.Generate(@interface, methods, events);
string serverProxySource = ServerProxy.Generate(@interface, methods, events);
string dispatcherSource = Dispatcher.Generate(@interface, methods, events);
string fileName = GetUniqueString(@interface.Name, fileNameCounter);
context.AddSource($"{fileName}.ClientProxy.g.cs", SourceText.From(clientProxySource, Encoding.UTF8));
context.AddSource($"{fileName}.ServerProxy.g.cs", SourceText.From(serverProxySource, Encoding.UTF8));
context.AddSource($"{fileName}.Dispatcher.g.cs", SourceText.From(dispatcherSource, Encoding.UTF8));
}
}
public void Initialize(GeneratorInitializationContext context)
{
context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
}
public static bool ContainsAttribute(INamedTypeSymbol symbol, INamedTypeSymbol attributeSymbol)
{
return symbol
.GetAttributes()
.Any(attribute => attribute.AttributeClass is not null &&
attribute.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default));
}
public static string GetUniqueString(string template, IDictionary<string, int> counter)
{
if (counter.TryGetValue(template, out int count))
{
return $"{template}{++count}";
}
counter[template] = count;
return template;
}
private class SyntaxReceiver : ISyntaxReceiver
{
public List<InterfaceDeclarationSyntax> CandidateInterfaces { get; } = new();
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if (syntaxNode is InterfaceDeclarationSyntax { AttributeLists: { Count: > 0 } } interfaceDeclarationSyntax)
{
CandidateInterfaces.Add(interfaceDeclarationSyntax);
}
}
}
}
}