Dreamine.MVVM.Generators 1.0.13
Dreamine.MVVM.Generators 프로젝트의 API와 구성 요소를 제공합니다.
로딩중...
검색중...
일치하는것 없음
DreamineEntryGenerator.cs
이 파일의 문서화 페이지로 가기
1using Microsoft.CodeAnalysis;
2using Microsoft.CodeAnalysis.CSharp.Syntax;
3using Microsoft.CodeAnalysis.Text;
4using System.Collections.Immutable;
5using System.Linq;
6using System.Text;
7
9{
14 [Generator]
15 public sealed class DreamineEntryGenerator : IIncrementalGenerator
16 {
17 private const string EntryAttributeMetadataName = "Dreamine.MVVM.Attributes.DreamineEntryAttribute";
18 private const string WpfApplicationMetadataName = "System.Windows.Application";
19
20 private static readonly DiagnosticDescriptor EntryMustBePartialDescriptor = new(
21 id: "DMG001",
22 title: "Entry class must be partial",
23 messageFormat: "Class '{0}' marked with [DreamineEntry] must be declared partial",
24 category: "Dreamine.MVVM.Generators",
25 defaultSeverity: DiagnosticSeverity.Error,
26 isEnabledByDefault: true);
27
28 private static readonly DiagnosticDescriptor EntryMustInheritApplicationDescriptor = new(
29 id: "DMG002",
30 title: "Entry class must inherit Application",
31 messageFormat: "Class '{0}' marked with [DreamineEntry] must inherit System.Windows.Application",
32 category: "Dreamine.MVVM.Generators",
33 defaultSeverity: DiagnosticSeverity.Error,
34 isEnabledByDefault: true);
35
36 private static readonly DiagnosticDescriptor DuplicateEntryDescriptor = new(
37 id: "DMG003",
38 title: "Only one entry class is allowed",
39 messageFormat: "Only one valid [DreamineEntry] class is allowed but '{0}' is also marked as an entry",
40 category: "Dreamine.MVVM.Generators",
41 defaultSeverity: DiagnosticSeverity.Error,
42 isEnabledByDefault: true);
43
48 public void Initialize(IncrementalGeneratorInitializationContext context)
49 {
50 IncrementalValueProvider<INamedTypeSymbol?> entryAttributeSymbolProvider =
51 context.CompilationProvider.Select(
52 static (compilation, _) => compilation.GetTypeByMetadataName(EntryAttributeMetadataName));
53
54 IncrementalValueProvider<INamedTypeSymbol?> applicationSymbolProvider =
55 context.CompilationProvider.Select(
56 static (compilation, _) => compilation.GetTypeByMetadataName(WpfApplicationMetadataName));
57
58 IncrementalValueProvider<(INamedTypeSymbol? EntryAttributeSymbol, INamedTypeSymbol? ApplicationSymbol)> symbolsProvider =
59 entryAttributeSymbolProvider.Combine(applicationSymbolProvider);
60
61 IncrementalValueProvider<ImmutableArray<EntryCandidateModel>> candidateProvider =
62 context.SyntaxProvider
63 .CreateSyntaxProvider(
64 predicate: static (node, _) => IsCandidateSyntax(node),
65 transform: static (syntaxContext, _) => syntaxContext)
66 .Combine(symbolsProvider)
67 .Select(static (pair, _) => TryCreateCandidate(pair.Left, pair.Right.EntryAttributeSymbol, pair.Right.ApplicationSymbol))
68 .Where(static candidate => candidate is not null)
69 .Select(static (candidate, _) => candidate!)
70 .Collect();
71
72 context.RegisterSourceOutput(candidateProvider, static (sourceProductionContext, candidates) =>
73 {
74 Emit(sourceProductionContext, candidates);
75 });
76 }
77
83 private static bool IsCandidateSyntax(SyntaxNode node)
84 {
85 return node is ClassDeclarationSyntax classDeclaration &&
86 classDeclaration.AttributeLists.Count > 0;
87 }
88
99 private static EntryCandidateModel? TryCreateCandidate(
100 GeneratorSyntaxContext context,
101 INamedTypeSymbol? entryAttributeSymbol,
102 INamedTypeSymbol? applicationSymbol)
103 {
104 if (entryAttributeSymbol is null || applicationSymbol is null)
105 {
106 return null;
107 }
108
109 if (context.Node is not ClassDeclarationSyntax classDeclaration)
110 {
111 return null;
112 }
113
114 if (context.SemanticModel.GetDeclaredSymbol(classDeclaration) is not INamedTypeSymbol classSymbol)
115 {
116 return null;
117 }
118
119 bool hasEntryAttribute = classSymbol
120 .GetAttributes()
121 .Any(attribute => SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, entryAttributeSymbol));
122
123 if (!hasEntryAttribute)
124 {
125 return null;
126 }
127
128 string namespaceName = classSymbol.ContainingNamespace is { IsGlobalNamespace: false }
129 ? classSymbol.ContainingNamespace.ToDisplayString()
130 : string.Empty;
131
132 return new EntryCandidateModel(
133 classSymbol,
134 classDeclaration,
135 namespaceName,
136 classSymbol.Name,
137 IsPartial(classDeclaration),
138 InheritsFrom(classSymbol, applicationSymbol));
139 }
140
146 private static void Emit(
147 SourceProductionContext context,
148 ImmutableArray<EntryCandidateModel> candidates)
149 {
150 if (candidates.IsDefaultOrEmpty)
151 {
152 return;
153 }
154
155 foreach (EntryCandidateModel candidate in candidates)
156 {
157 if (!candidate.IsPartial)
158 {
159 context.ReportDiagnostic(Diagnostic.Create(
161 candidate.ClassDeclaration.Identifier.GetLocation(),
162 candidate.ClassName));
163 }
164
165 if (!candidate.IsApplicationDerived)
166 {
167 context.ReportDiagnostic(Diagnostic.Create(
169 candidate.ClassDeclaration.Identifier.GetLocation(),
170 candidate.ClassName));
171 }
172 }
173
174 ImmutableArray<EntryCandidateModel> validCandidates = candidates
175 .Where(static candidate => candidate.IsPartial && candidate.IsApplicationDerived)
176 .ToImmutableArray();
177
178 if (validCandidates.Length == 0)
179 {
180 return;
181 }
182
183 if (validCandidates.Length > 1)
184 {
185 foreach (EntryCandidateModel candidate in validCandidates)
186 {
187 context.ReportDiagnostic(Diagnostic.Create(
189 candidate.ClassDeclaration.Identifier.GetLocation(),
190 candidate.ClassName));
191 }
192
193 return;
194 }
195
196 EntryCandidateModel entryCandidate = validCandidates[0];
197 string source = BuildEntrySource(entryCandidate.Namespace, entryCandidate.ClassName);
198
199 context.AddSource(
200 $"{entryCandidate.ClassName}.DreamineEntry.g.cs",
201 SourceText.From(source, Encoding.UTF8));
202 }
203
209 private static bool IsPartial(ClassDeclarationSyntax classDeclaration)
210 {
211 return classDeclaration.Modifiers.Any(static modifier => modifier.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.PartialKeyword));
212 }
213
220 private static bool InheritsFrom(INamedTypeSymbol symbol, INamedTypeSymbol baseTypeSymbol)
221 {
222 INamedTypeSymbol? current = symbol;
223
224 while (current is not null)
225 {
226 if (SymbolEqualityComparer.Default.Equals(current, baseTypeSymbol))
227 {
228 return true;
229 }
230
231 current = current.BaseType;
232 }
233
234 return false;
235 }
236
243 private static string BuildEntrySource(string namespaceName, string className)
244 {
245 string namespaceBlockStart = string.IsNullOrWhiteSpace(namespaceName)
246 ? string.Empty
247 : $"namespace {namespaceName}\r\n{{";
248
249 string namespaceBlockEnd = string.IsNullOrWhiteSpace(namespaceName)
250 ? string.Empty
251 : "}";
252
253 return $$"""
254// <auto-generated />
255#nullable enable
256using System.Windows;
257using Dreamine.MVVM.Wpf;
258
259{{namespaceBlockStart}}
260 public partial class {{className}}
261 {
266 protected override void OnStartup(StartupEventArgs e)
267 {
268 base.OnStartup(e);
269
270 var rootAssembly = typeof({{className}}).Assembly;
271
272 RegisterBefore();
273
274 DreamineWpfOptions options = DreamineWpfOptions.CreateDefault();
275 ConfigureDreamine(options);
276
277 DreamineAppBuilder.Initialize(rootAssembly, options);
278
279 ShowMainWindow();
280 Dispatcher.InvokeAsync(() => RegisterAfter());
281 }
282
286 static partial void RegisterBefore();
287
292 static partial void ConfigureDreamine(DreamineWpfOptions options);
293
297 static partial void RegisterAfter();
298
302 static partial void ShowMainWindow();
303 }
304{{namespaceBlockEnd}}
305""";
306 }
307 }
308}
DreamineEntryAttribute가 적용된 WPF 엔트리 클래스를 분석하고 Dreamine 부트스트랩 코드를 생성하는 증분 생성기입니다.
static readonly DiagnosticDescriptor EntryMustBePartialDescriptor
void Initialize(IncrementalGeneratorInitializationContext context)
증분 생성기 파이프라인을 초기화합니다.
static bool IsPartial(ClassDeclarationSyntax classDeclaration)
클래스 선언이 partial인지 확인합니다.
static ? EntryCandidateModel TryCreateCandidate(GeneratorSyntaxContext context, INamedTypeSymbol? entryAttributeSymbol, INamedTypeSymbol? applicationSymbol)
구문/시맨틱 정보를 기반으로 엔트리 후보 모델을 생성합니다.
static readonly DiagnosticDescriptor EntryMustInheritApplicationDescriptor
static void Emit(SourceProductionContext context, ImmutableArray< EntryCandidateModel > candidates)
수집된 엔트리 후보를 진단하고 소스를 생성합니다.
static readonly DiagnosticDescriptor DuplicateEntryDescriptor
static bool InheritsFrom(INamedTypeSymbol symbol, INamedTypeSymbol baseTypeSymbol)
대상 클래스가 지정한 기반 타입을 상속하는지 확인합니다.
static bool IsCandidateSyntax(SyntaxNode node)
엔트리 후보가 될 수 있는 구문인지 확인합니다.
static string BuildEntrySource(string namespaceName, string className)
엔트리 부트스트랩 소스 코드를 생성합니다.