1+ #nullable enable
12using Microsoft . CodeAnalysis ;
23using Microsoft . CodeAnalysis . Text ;
34using System ;
45using System . Collections . Generic ;
6+ using System . Collections . Immutable ;
57using System . IO ;
68using System . Linq ;
79using System . Text ;
@@ -14,54 +16,70 @@ public class WrapperElementsGenerator : IIncrementalGenerator
1416 public void Initialize ( IncrementalGeneratorInitializationContext context )
1517 {
1618 // Finds the AngleSharp assembly referenced by the target project
17- // This should prevent the source generator from running unless a
18- // new symbol is returned.
19- var angleSharpAssemblyReference = context
19+ // and collects element interface type names into cacheable records.
20+ var elementInterfaces = context
2021 . CompilationProvider
2122 . Select ( ( compilation , cancellationToken ) =>
2223 {
2324 var meta = compilation . References . FirstOrDefault ( x => x . Display ? . EndsWith ( $ "{ Path . DirectorySeparatorChar } AngleSharp.dll", StringComparison . Ordinal ) ?? false ) ;
24- return compilation . GetAssemblyOrModuleSymbol ( meta ) ;
25+ var assembly = compilation . GetAssemblyOrModuleSymbol ( meta ) ;
26+
27+ if ( assembly is not IAssemblySymbol angleSharpAssembly )
28+ return null ;
29+
30+ var elementInterfaceTypes = FindElementInterfaces ( angleSharpAssembly ) ;
31+ // Create cacheable records with just the essential info needed for generation
32+ return new ElementInterfacesData (
33+ angleSharpAssembly ,
34+ elementInterfaceTypes . Select ( t => new ElementTypeInfo (
35+ t . Name ,
36+ t . ToDisplayString ( GeneratorConfig . SymbolFormat )
37+ ) ) . ToImmutableArray ( ) ) ;
2538 } ) ;
2639
2740 // Output the hardcoded source files
28- context . RegisterSourceOutput ( angleSharpAssemblyReference , GenerateStaticContent ) ;
41+ context . RegisterSourceOutput ( elementInterfaces , GenerateStaticContent ) ;
2942
3043 // Output the generated wrapper types
31- context . RegisterSourceOutput ( angleSharpAssemblyReference , GenerateWrapperTypes ) ;
44+ context . RegisterSourceOutput ( elementInterfaces , GenerateWrapperTypes ) ;
3245 }
3346
34- private static void GenerateStaticContent ( SourceProductionContext context , ISymbol assembly )
47+ private static void GenerateStaticContent ( SourceProductionContext context , ElementInterfacesData ? data )
3548 {
36- if ( assembly is not IAssemblySymbol )
49+ if ( data is null )
3750 return ;
3851
3952 context . AddSource ( "IElementWrapperFactory.g.cs" , ReadEmbeddedResource ( "Bunit.Web.AngleSharp.IElementWrapperFactory.cs" ) ) ;
4053 context . AddSource ( "IElementWrapper.g.cs" , ReadEmbeddedResource ( "Bunit.Web.AngleSharp.IElementWrapper.cs" ) ) ;
4154 context . AddSource ( "WrapperBase.g.cs" , ReadEmbeddedResource ( "Bunit.Web.AngleSharp.WrapperBase.cs" ) ) ;
4255 }
4356
44- private static void GenerateWrapperTypes ( SourceProductionContext context , ISymbol assembly )
57+ private static void GenerateWrapperTypes ( SourceProductionContext context , ElementInterfacesData ? data )
4558 {
46- if ( assembly is not IAssemblySymbol angleSharpAssembly )
59+ if ( data is null )
4760 return ;
4861
49- var elementInterfacetypes = FindElementInterfaces ( angleSharpAssembly ) ;
62+ // Retrieve the actual symbols from the assembly for code generation
63+ var elementSymbols = data . ElementTypes
64+ . Select ( t => data . Assembly . GetTypeByMetadataName ( t . FullyQualifiedName . Replace ( "global::" , "" ) ) )
65+ . Where ( s => s is not null )
66+ . Cast < INamedTypeSymbol > ( )
67+ . ToList ( ) ;
5068
5169 var source = new StringBuilder ( ) ;
52- foreach ( var elm in elementInterfacetypes )
70+ foreach ( var elm in elementSymbols )
5371 {
5472 source . Clear ( ) ;
5573 var name = WrapperElementGenerator . GenerateWrapperTypeSource ( source , elm ) ;
5674 context . AddSource ( $ "{ name } .g.cs", SourceText . From ( source . ToString ( ) , Encoding . UTF8 ) ) ;
5775 }
5876
5977 source . Clear ( ) ;
60- GenerateWrapperFactory ( source , elementInterfacetypes ) ;
78+ GenerateWrapperFactory ( source , data . ElementTypes ) ;
6179 context . AddSource ( $ "WrapperExtensions.g.cs", SourceText . From ( source . ToString ( ) , Encoding . UTF8 ) ) ;
6280 }
6381
64- private static void GenerateWrapperFactory ( StringBuilder source , IEnumerable < INamedTypeSymbol > elementInterfacetypes )
82+ private static void GenerateWrapperFactory ( StringBuilder source , ImmutableArray < ElementTypeInfo > elementTypes )
6583 {
6684 source . AppendLine ( """namespace Bunit.Web.AngleSharp;""" ) ;
6785 source . AppendLine ( ) ;
@@ -78,10 +96,10 @@ private static void GenerateWrapperFactory(StringBuilder source, IEnumerable<INa
7896 source . AppendLine ( $ "\t public static global::AngleSharp.Dom.IElement WrapUsing<TElementFactory>(this global::AngleSharp.Dom.IElement element, TElementFactory elementFactory) where TElementFactory : Bunit.Web.AngleSharp.IElementWrapperFactory => element switch") ;
7997 source . AppendLine ( "\t {" ) ;
8098
81- foreach ( var elm in elementInterfacetypes )
99+ foreach ( var elm in elementTypes )
82100 {
83101 var wrapperName = $ "{ elm . Name . Substring ( 1 ) } Wrapper";
84- source . AppendLine ( $ "\t \t { elm . ToDisplayString ( GeneratorConfig . SymbolFormat ) } e => new { wrapperName } (e, elementFactory),") ;
102+ source . AppendLine ( $ "\t \t { elm . FullyQualifiedName } e => new { wrapperName } (e, elementFactory),") ;
85103 }
86104
87105 source . AppendLine ( $ "\t \t _ => new ElementWrapper(element, elementFactory),") ;
@@ -104,6 +122,9 @@ private static IReadOnlyList<INamedTypeSymbol> FindElementInterfaces(IAssemblySy
104122 var elementInterfaceSymbol = angleSharpAssembly
105123 . GetTypeByMetadataName ( "AngleSharp.Dom.IElement" ) ;
106124
125+ if ( elementInterfaceSymbol is null )
126+ return Array . Empty < INamedTypeSymbol > ( ) ;
127+
107128 var result = htmlDomNamespace
108129 . GetTypeMembers ( )
109130 . Where ( typeSymbol => typeSymbol . TypeKind == TypeKind . Interface && typeSymbol . AllInterfaces . Contains ( elementInterfaceSymbol ) )
@@ -139,3 +160,13 @@ private static string ReadEmbeddedResource(string resourceName)
139160 return reader . ReadToEnd ( ) ;
140161 }
141162}
163+
164+ // Cacheable data structure that stores minimal information about element interfaces
165+ // This allows the incremental generator to cache and reuse results across builds
166+ internal sealed record ElementInterfacesData (
167+ IAssemblySymbol Assembly ,
168+ ImmutableArray < ElementTypeInfo > ElementTypes ) ;
169+
170+ internal sealed record ElementTypeInfo (
171+ string Name ,
172+ string FullyQualifiedName ) ;
0 commit comments