1
1
// Copyright (c) Microsoft Corporation. All rights reserved.
2
2
// Licensed under the MIT License.
3
3
4
+ using System ;
5
+ using System . Collections . Generic ;
6
+ using System . Collections . Immutable ;
4
7
using System . Linq ;
5
8
using System . Text ;
9
+ using System . Text . RegularExpressions ;
6
10
using Microsoft . CodeAnalysis ;
11
+ using Microsoft . CodeAnalysis . CSharp . Syntax ;
7
12
using Microsoft . CodeAnalysis . Text ;
8
13
9
14
namespace Azure . EventGrid . Messaging . SourceGeneration
@@ -13,38 +18,144 @@ namespace Azure.EventGrid.Messaging.SourceGeneration
13
18
/// from constant values to deserialization method for each system event.
14
19
/// </summary>
15
20
[ Generator ]
16
- internal class EventGridSourceGenerator : ISourceGenerator
21
+ internal class EventGridSourceGenerator : IIncrementalGenerator
17
22
{
18
- private SourceVisitor _visitor ;
19
- private bool _isSystemEventsLibrary ;
20
23
private const string Indent = " " ;
21
24
22
- public void Execute ( GeneratorExecutionContext context )
25
+ // the event name is either 3 or 4 parts, e.g. Microsoft.AppConfiguration.KeyValueDeleted or Microsoft.ResourceNotifications.HealthResources.AvailabilityStatusChanged
26
+ private static readonly Regex EventTypeRegex = new ( "[a-zA-Z]+\\ .[a-zA-Z]+\\ .[a-zA-Z]+(\\ .[a-zA-Z]+)?" , RegexOptions . Compiled ) ;
27
+
28
+ private static ReadOnlySpan < char > SummaryStartTag => "<summary>" . AsSpan ( ) ;
29
+ private static ReadOnlySpan < char > SummaryEndTag => "</summary>" . AsSpan ( ) ;
30
+
31
+ public void Initialize ( IncrementalGeneratorInitializationContext context )
23
32
{
24
- _visitor = new SourceVisitor ( ) ;
25
- _isSystemEventsLibrary = context . Compilation . AssemblyName == "Azure.Messaging.EventGrid.SystemEvents" ;
26
- var root = context . Compilation . GetSymbolsWithName (
27
- "SystemEvents" ,
28
- SymbolFilter . Namespace )
29
- . Single ( ) ;
30
- _visitor . Visit ( root ) ;
31
-
32
- context . AddSource ( "SystemEventNames.cs" , SourceText . From ( ConstructSystemEventNames ( ) , Encoding . UTF8 ) ) ;
33
- context . AddSource ( "SystemEventExtensions.cs" , SourceText . From ( ConstructSystemEventExtensions ( ) , Encoding . UTF8 ) ) ;
33
+ // Get all class declarations that end with "EventData"
34
+ var classDeclarations = context . SyntaxProvider
35
+ . CreateSyntaxProvider (
36
+ predicate : static ( s , _ ) => s is ClassDeclarationSyntax cds && cds . Identifier . Text . EndsWith ( "EventData" ) ,
37
+ transform : static ( ctx , cancellationToken ) =>
38
+ {
39
+ var semanticModel = ctx . SemanticModel ;
40
+ var classDeclaration = ( ClassDeclarationSyntax ) ctx . Node ;
41
+
42
+ var declaredSymbol = semanticModel . GetDeclaredSymbol ( classDeclaration , cancellationToken ) ;
43
+
44
+ return declaredSymbol ? . ContainingNamespace is { Name : "SystemEvents" } ? classDeclaration : null ;
45
+ } )
46
+ . Where ( static cls => cls != null ) ;
47
+
48
+ var compilationAndClasses = context . CompilationProvider . Combine ( classDeclarations . Collect ( ) ) ;
49
+
50
+ // Generate the source
51
+ context . RegisterSourceOutput ( compilationAndClasses ,
52
+ static ( SourceProductionContext sourceProductionContext , ( Compilation Compilation , ImmutableArray < ClassDeclarationSyntax > ClassDeclarations ) input ) =>
53
+ {
54
+ Execute ( sourceProductionContext , input . Compilation , input . ClassDeclarations ) ;
55
+ } ) ;
56
+ }
57
+
58
+ private static void Execute ( SourceProductionContext context , Compilation compilation , ImmutableArray < ClassDeclarationSyntax > classes )
59
+ {
60
+ if ( classes . IsDefaultOrEmpty )
61
+ {
62
+ return ;
63
+ }
64
+
65
+ var systemEventNodes = GetSystemEventNodes ( compilation , classes ) ;
66
+ if ( systemEventNodes . Count <= 0 )
67
+ {
68
+ return ;
69
+ }
70
+
71
+ var isSystemEventsLibrary = compilation . AssemblyName == "Azure.Messaging.EventGrid.SystemEvents" ;
72
+
73
+ context . AddSource ( "SystemEventNames.cs" , SourceText . From ( ConstructSystemEventNames ( systemEventNodes , isSystemEventsLibrary ) , Encoding . UTF8 ) ) ;
74
+ context . AddSource ( "SystemEventExtensions.cs" , SourceText . From ( ConstructSystemEventExtensions ( systemEventNodes , isSystemEventsLibrary ) , Encoding . UTF8 ) ) ;
75
+ }
76
+
77
+ private static List < SystemEventNode > GetSystemEventNodes ( Compilation compilation , ImmutableArray < ClassDeclarationSyntax > classes )
78
+ {
79
+ var systemEventNodes = new List < SystemEventNode > ( ) ;
80
+ var eventTypeSet = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ;
81
+
82
+ foreach ( var classDeclaration in classes )
83
+ {
84
+ var semanticModel = compilation . GetSemanticModel ( classDeclaration . SyntaxTree ) ;
85
+ if ( semanticModel . GetDeclaredSymbol ( classDeclaration ) is not INamedTypeSymbol classSymbol )
86
+ {
87
+ continue ;
88
+ }
89
+
90
+ var documentationCommentXml = classSymbol . GetDocumentationCommentXml ( ) ;
91
+ if ( string . IsNullOrEmpty ( documentationCommentXml ) )
92
+ {
93
+ continue ;
94
+ }
95
+
96
+ // Extract event type from documentation comments
97
+ string eventType = ExtractEventTypeFromDocumentation ( documentationCommentXml ) ;
98
+ if ( string . IsNullOrEmpty ( eventType ) )
99
+ {
100
+ // Skip if no event type is found (likely a base type)
101
+ continue ;
102
+ }
103
+
104
+ if ( ! eventTypeSet . Add ( eventType ) )
105
+ {
106
+ continue ;
107
+ }
108
+
109
+ // Find the deserialize method
110
+ var deserializeMethod = classSymbol . GetMembers ( )
111
+ . OfType < IMethodSymbol > ( )
112
+ . FirstOrDefault ( m => m . Name . StartsWith ( "Deserialize" , StringComparison . Ordinal ) ) ? . Name ;
113
+
114
+ if ( deserializeMethod == null )
115
+ {
116
+ // Skip if no deserialize method is found
117
+ continue ;
118
+ }
119
+
120
+ // Create a SystemEventNode for this event
121
+ systemEventNodes . Add ( new SystemEventNode ( eventName : classSymbol . Name , eventType : $@ """{ eventType } """, deserializeMethod: deserializeMethod));
122
+ }
123
+
124
+ return systemEventNodes;
34
125
}
35
126
36
- public void Initialize ( GeneratorInitializationContext context )
127
+ private static string ExtractEventTypeFromDocumentation(string documentationCommentXml )
37
128
{
38
- // Uncomment to debug
39
- //if (!Debugger.IsAttached)
40
- //{
41
- // Debugger.Launch();
42
- //}
129
+ if ( string . IsNullOrEmpty ( documentationCommentXml ) )
130
+ {
131
+ return null ;
132
+ }
133
+
134
+ ReadOnlySpan< char > docSpan = documentationCommentXml. AsSpan( ) ;
135
+
136
+ int summaryStartIndex = docSpan . IndexOf ( SummaryStartTag ) ;
137
+ if ( summaryStartIndex < 0 )
138
+ {
139
+ return null ;
140
+ }
141
+
142
+ summaryStartIndex += SummaryStartTag. Length ;
143
+
144
+ int summaryEndIndex = docSpan . Slice ( summaryStartIndex ) . IndexOf ( SummaryEndTag ) ;
145
+ if ( summaryEndIndex < 0 )
146
+ {
147
+ return null ;
148
+ }
149
+
150
+ var summaryContent = docSpan . Slice ( summaryStartIndex , summaryEndIndex ) ;
151
+
152
+ var match = EventTypeRegex . Match ( summaryContent . ToString ( ) ) ;
153
+ return match . Success ? match . Value : null ;
43
154
}
44
155
45
- private string ConstructSystemEventNames ( )
156
+ private static string ConstructSystemEventNames( List < SystemEventNode > systemEvents , bool isSystemEventsLibrary )
46
157
{
47
- string ns = _isSystemEventsLibrary ? "Azure.Messaging.EventGrid.SystemEvents" : "Azure.Messaging.EventGrid" ;
158
+ string ns = isSystemEventsLibrary ? "Azure.Messaging.EventGrid.SystemEvents" : "Azure.Messaging.EventGrid" ;
48
159
var sourceBuilder = new StringBuilder (
49
160
$@ "// Copyright (c) Microsoft Corporation. All rights reserved.
50
161
// Licensed under the MIT License.
@@ -62,34 +173,34 @@ namespace {ns}
62
173
public static class SystemEventNames
63
174
{{
64
175
" ) ;
65
- for ( int i = 0 ; i < _visitor . SystemEvents . Count ; i ++ )
176
+ for ( int i = 0 ; i < systemEvents . Count ; i++ )
66
177
{
67
178
if ( i > 0 )
68
179
{
69
180
sourceBuilder. AppendLine( ) ;
70
181
}
71
- SystemEventNode sysEvent = _visitor . SystemEvents [ i ] ;
182
+ SystemEventNode sysEvent = systemEvents [ i] ;
72
183
73
184
// Add the ref docs for each constant
74
- sourceBuilder . AppendLine ( $ " { Indent } { Indent } /// <summary>") ;
75
- sourceBuilder . AppendLine (
76
- ! _isSystemEventsLibrary
77
- ? $ " { Indent } { Indent } /// The value of the Event Type stored in <see cref=\" EventGridEvent.EventType\" /> and <see cref=\" CloudEvent.Type\" /> "
78
- : $ " { Indent } { Indent } /// The value of the Event Type stored in <see cref=\" CloudEvent.Type\" /> ") ;
185
+ sourceBuilder. AppendIndentedLine ( 2 , " /// <summary>");
186
+ sourceBuilder. AppendIndentedLine ( 2 ,
187
+ ! isSystemEventsLibrary
188
+ ? " /// The value of the Event Type stored in <see cref=\"EventGridEvent.EventType\"/> and <see cref=\"CloudEvent.Type\"/> "
189
+ : " /// The value of the Event Type stored in <see cref=\"CloudEvent.Type\"/> ");
79
190
80
- sourceBuilder . AppendLine ( $ " { Indent } { Indent } /// for the <see cref=\" { sysEvent . EventName } \" /> system event.") ;
81
- sourceBuilder . AppendLine ( $ " { Indent } { Indent } /// </summary>") ;
191
+ sourceBuilder. AppendIndentedLine ( 2 , $" /// for the <see cref=\"{sysEvent.EventName}\"/> system event.");
192
+ sourceBuilder. AppendIndentedLine ( 2 , " /// </summary>");
82
193
83
194
// Add the constant
84
- sourceBuilder . AppendLine ( $ " { Indent } { Indent } public const string { sysEvent . EventConstantName } = { sysEvent . EventType } ;") ;
195
+ sourceBuilder. AppendIndentedLine ( 2 , $" public const string { sysEvent. EventConstantName} = { sysEvent. EventType} ; ") ;
85
196
}
86
197
87
- sourceBuilder . Append ( $@ " { Indent } } }
88
- }} " ) ;
198
+ sourceBuilder. AppendIndentedLine ( 1 , @" }
199
+ }" ) ;
89
200
return sourceBuilder. ToString( ) ;
90
201
}
91
202
92
- private string ConstructSystemEventExtensions ( )
203
+ private static string ConstructSystemEventExtensions( List < SystemEventNode > systemEvents , bool isSystemEventsLibrary )
93
204
{
94
205
var sourceBuilder = new StringBuilder (
95
206
$@ "// Copyright (c) Microsoft Corporation. All rights reserved.
@@ -101,7 +212,7 @@ private string ConstructSystemEventExtensions()
101
212
using System.Collections.Generic;
102
213
using System.Text.Json;
103
214
using Azure.Messaging.EventGrid.SystemEvents;
104
- { ( _isSystemEventsLibrary ? "using System.ClientModel.Primitives;" : string . Empty ) }
215
+ { ( isSystemEventsLibrary ? "using System.ClientModel.Primitives;" : string . Empty ) }
105
216
106
217
namespace Azure.Messaging.EventGrid
107
218
{{
@@ -111,17 +222,17 @@ public static object AsSystemEventData(string eventType, JsonElement data)
111
222
{{
112
223
var eventTypeSpan = eventType.AsSpan();
113
224
" ) ;
114
- foreach ( SystemEventNode sysEvent in _visitor . SystemEvents )
225
+ foreach ( SystemEventNode sysEvent in systemEvents )
115
226
{
116
227
// Add each an entry for each system event to the dictionary containing a mapping from constant name to deserialization method.
117
- sourceBuilder . AppendLine (
118
- $ "{ Indent } { Indent } { Indent } if (eventTypeSpan.Equals(SystemEventNames.{ sysEvent . EventConstantName } .AsSpan(), StringComparison.OrdinalIgnoreCase))") ;
119
- sourceBuilder . AppendLine (
120
- $ "{ Indent } { Indent } { Indent } { Indent } return { sysEvent . EventName } .{ sysEvent . DeserializeMethod } (data{ ( _isSystemEventsLibrary ? ", null" : string . Empty ) } );") ;
228
+ sourceBuilder. AppendIndentedLine ( 3 ,
229
+ $ "if (eventTypeSpan.Equals(SystemEventNames.{ sysEvent . EventConstantName } .AsSpan(), StringComparison.OrdinalIgnoreCase))") ;
230
+ sourceBuilder. AppendIndentedLine ( 4 ,
231
+ $ "return { sysEvent . EventName } .{ sysEvent . DeserializeMethod } (data{ ( isSystemEventsLibrary ? ", null" : string . Empty ) } );") ;
121
232
}
122
- sourceBuilder . AppendLine ( $ " { Indent } { Indent } { Indent } return null;") ;
123
- sourceBuilder . AppendLine ( $ " { Indent } { Indent } } }") ;
124
- sourceBuilder . AppendLine ( $ " { Indent } } }") ;
233
+ sourceBuilder. AppendIndentedLine ( 3 , " return null;") ;
234
+ sourceBuilder. AppendIndentedLine ( 2 , " }") ;
235
+ sourceBuilder. AppendIndentedLine ( 1 , " }") ;
125
236
sourceBuilder. AppendLine ( "}" ) ;
126
237
127
238
return sourceBuilder. ToString ( ) ;
0 commit comments