11// Copyright (c) Microsoft Corporation. All rights reserved.
22// Licensed under the MIT License.
33
4+ using System ;
5+ using System . Collections . Generic ;
6+ using System . Collections . Immutable ;
47using System . Linq ;
58using System . Text ;
9+ using System . Text . RegularExpressions ;
610using Microsoft . CodeAnalysis ;
11+ using Microsoft . CodeAnalysis . CSharp . Syntax ;
712using Microsoft . CodeAnalysis . Text ;
813
914namespace Azure . EventGrid . Messaging . SourceGeneration
@@ -13,38 +18,144 @@ namespace Azure.EventGrid.Messaging.SourceGeneration
1318 /// from constant values to deserialization method for each system event.
1419 /// </summary>
1520 [ Generator ]
16- internal class EventGridSourceGenerator : ISourceGenerator
21+ internal class EventGridSourceGenerator : IIncrementalGenerator
1722 {
18- private SourceVisitor _visitor ;
19- private bool _isSystemEventsLibrary ;
2023 private const string Indent = " " ;
2124
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 )
2332 {
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;
34125 }
35126
36- public void Initialize ( GeneratorInitializationContext context )
127+ private static string ExtractEventTypeFromDocumentation(string documentationCommentXml )
37128 {
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 ;
43154 }
44155
45- private string ConstructSystemEventNames ( )
156+ private static string ConstructSystemEventNames( List < SystemEventNode > systemEvents , bool isSystemEventsLibrary )
46157 {
47- string ns = _isSystemEventsLibrary ? "Azure.Messaging.EventGrid.SystemEvents" : "Azure.Messaging.EventGrid" ;
158+ string ns = isSystemEventsLibrary ? "Azure.Messaging.EventGrid.SystemEvents" : "Azure.Messaging.EventGrid" ;
48159 var sourceBuilder = new StringBuilder (
49160$@ "// Copyright (c) Microsoft Corporation. All rights reserved.
50161// Licensed under the MIT License.
@@ -62,34 +173,34 @@ namespace {ns}
62173 public static class SystemEventNames
63174 {{
64175" ) ;
65- for ( int i = 0 ; i < _visitor . SystemEvents . Count ; i ++ )
176+ for ( int i = 0 ; i < systemEvents . Count ; i++ )
66177 {
67178 if ( i > 0 )
68179 {
69180 sourceBuilder. AppendLine( ) ;
70181 }
71- SystemEventNode sysEvent = _visitor . SystemEvents [ i ] ;
182+ SystemEventNode sysEvent = systemEvents [ i] ;
72183
73184 // 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\"/> ");
79190
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>");
82193
83194 // 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} ; ") ;
85196 }
86197
87- sourceBuilder . Append ( $@ " { Indent } } }
88- }} " ) ;
198+ sourceBuilder. AppendIndentedLine ( 1 , @" }
199+ }" ) ;
89200 return sourceBuilder. ToString( ) ;
90201 }
91202
92- private string ConstructSystemEventExtensions ( )
203+ private static string ConstructSystemEventExtensions( List < SystemEventNode > systemEvents , bool isSystemEventsLibrary )
93204 {
94205 var sourceBuilder = new StringBuilder (
95206 $@ "// Copyright (c) Microsoft Corporation. All rights reserved.
@@ -101,7 +212,7 @@ private string ConstructSystemEventExtensions()
101212using System.Collections.Generic;
102213using System.Text.Json;
103214using Azure.Messaging.EventGrid.SystemEvents;
104- { ( _isSystemEventsLibrary ? "using System.ClientModel.Primitives;" : string . Empty ) }
215+ { ( isSystemEventsLibrary ? "using System.ClientModel.Primitives;" : string . Empty ) }
105216
106217namespace Azure.Messaging.EventGrid
107218{{
@@ -111,17 +222,17 @@ public static object AsSystemEventData(string eventType, JsonElement data)
111222 {{
112223 var eventTypeSpan = eventType.AsSpan();
113224" ) ;
114- foreach ( SystemEventNode sysEvent in _visitor . SystemEvents )
225+ foreach ( SystemEventNode sysEvent in systemEvents )
115226 {
116227 // 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 ) } );") ;
121232 }
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 , " }") ;
125236 sourceBuilder. AppendLine ( "}" ) ;
126237
127238 return sourceBuilder. ToString ( ) ;
0 commit comments