forked from SnowflakePowered/snowflake
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathConfigurationCollectionGenerator.cs
154 lines (136 loc) · 6.49 KB
/
ConfigurationCollectionGenerator.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
153
154
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using Snowflake.Language.Analyzers;
using Snowflake.Language.Analyzers.Configuration;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
namespace Snowflake.Language.Generators.Configuration
{
[Generator]
public sealed class ConfigurationCollectionGenerator : ISourceGenerator
{
private static ImmutableArray<AbstractSyntaxNodeAnalyzer> Analyzers
= ImmutableArray.Create<AbstractSyntaxNodeAnalyzer>(
new TemplateInterfaceTopLevelAnalyzer(),
new UnextendibleInterfaceAnalyzer(),
new CannotHideInheritedPropertyAnalyzer(),
new InvalidTemplateAccessorAnalyzer(),
new CollectionPropertyNotSectionAnalyzer(),
new CollectionPropertyInvalidAccessorAnalyzer(),
new CollectionPropertyMustHaveGetterAnalyzer(),
new DuplicateConfigurationTargetAnalyzer()
);
public void Execute(GeneratorExecutionContext context)
{
if (context.SyntaxReceiver is not ConfigurationTemplateInterfaceSyntaxReceiver receiver)
return;
var compilation = context.Compilation;
var types = new ConfigurationTypes(compilation);
if (!types.CheckContext(context))
return;
foreach (var ifaceSyntax in receiver.CandidateInterfaces)
{
var model = compilation.GetSemanticModel(ifaceSyntax.SyntaxTree);
var ifaceSymbol = model.GetDeclaredSymbol(ifaceSyntax, context.CancellationToken);
if (ifaceSymbol == null)
continue;
if (!ifaceSymbol.GetAttributes()
.Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, types.ConfigurationCollectionAttribute)))
continue;
var diagnostics = Analyzers.AsParallel()
.SelectMany(a => a.Analyze(context.Compilation, model, ifaceSyntax, context.CancellationToken))
.ToList();
foreach (var diag in diagnostics)
{
context.ReportDiagnostic(diag);
}
if (diagnostics.Any())
return;
var properties = new List<(INamedTypeSymbol, IPropertySymbol)>();
// Add collection props bottom up.
foreach (var childIface in ifaceSymbol.AllInterfaces.Reverse().Concat(new[] { ifaceSymbol }))
{
foreach (var member in childIface.GetMembers())
{
if (member is IPropertySymbol property)
{
properties.Add((childIface, property!));
}
}
}
string classSource = GenerateSource(ifaceSymbol, properties, types);
context.AddSource($"{ifaceSymbol.Name}_ConfigurationCollectionProxy.cs", SourceText.From(classSource, Encoding.UTF8));
}
}
private string GenerateSource(INamedTypeSymbol declaringInterface,
List<(INamedTypeSymbol, IPropertySymbol)> properties,
ConfigurationTypes types)
{
string namespaceName = declaringInterface.ContainingNamespace.ToDisplayString();
string generatedNamespaceName = $"Snowflake.Configuration.GeneratedConfigurationProxies.Collection_{namespaceName}";
string tag = RandomTag.Tag(6);
string backingClassName = $"{declaringInterface.Name}Proxy_{tag}";
StringBuilder source = new StringBuilder($@"
namespace {namespaceName}
{{
[{types.ConfigurationGenerationInstanceAttribute.ToDisplayString()}(typeof({generatedNamespaceName}.{backingClassName}))]
public partial interface {declaringInterface.Name}
: {types.IConfigurationCollectionTemplate.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}
{{
}}
}}
namespace {generatedNamespaceName}
{{
using System.Collections.Generic;
using System.Collections.Immutable;
sealed class {backingClassName} : {declaringInterface.ToDisplayString()}
{{
readonly {types.IConfigurationValueCollection.ToDisplayString()} __backingCollection;
readonly Dictionary<string, {types.IConfigurationSection.ToDisplayString()}> __configurationSections;
IReadOnlyDictionary<string, {types.IConfigurationSection.ToDisplayString()}>
{types.IConfigurationCollectionTemplate.ToDisplayString()}.GetValueDictionary()
=> ImmutableDictionary.CreateRange(__configurationSections);
private {backingClassName}({types.IConfigurationValueCollection.ToDisplayString()} collection)
{{
this.__backingCollection = collection;
this.__configurationSections = new Dictionary<string, {types.IConfigurationSection.ToDisplayString()}>();
");
foreach ((var iface, var prop) in properties)
{
// We can't parameterize ConfigurationSection<T> because Snowflake.Framework needs to consume
// Snowflake.Framework.Generator for EmptyPluginConfiguration, and it is not available at generator time.
source.Append($@"
this.backing__{prop.Name} = new Snowflake.Configuration.ConfigurationSection<{prop.Type.ToDisplayString()}>(this.__backingCollection, nameof({iface.ToDisplayString()}.{prop.Name}));
this.__configurationSections[nameof({iface.ToDisplayString()}.{prop.Name})] = this.backing__{prop.Name};
");
}
source.Append("}");
foreach ((var iface, var prop) in properties)
{
source.Append($@"
private {types.IConfigurationSection.ToDisplayString()}<{prop.Type.ToDisplayString()}> backing__{prop.Name};
{prop.Type.ToDisplayString()} {iface.ToDisplayString()}.{prop.Name}
{{
get {{ return this.backing__{prop.Name}.Configuration; }}
}}
");
}
source.Append("}}");
return source.ToString();
}
public void Initialize(GeneratorInitializationContext context)
{
#if DEBUG
//if (!Debugger.IsAttached)
//{
// Debugger.Launch();
//}
#endif
context.RegisterForSyntaxNotifications(() => new ConfigurationTemplateInterfaceSyntaxReceiver("ConfigurationCollection"));
}
}
}