Skip to content

Commit 4305df7

Browse files
Copilotlinkdotnet
andcommitted
Make WrapperElementsGenerator cacheable with collect-and-execute pattern
Co-authored-by: linkdotnet <[email protected]>
1 parent ef40940 commit 4305df7

File tree

1 file changed

+47
-16
lines changed

1 file changed

+47
-16
lines changed

src/bunit.generators.internal/Web.AngleSharp/WrapperElementsGenerator.cs

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
#nullable enable
12
using Microsoft.CodeAnalysis;
23
using Microsoft.CodeAnalysis.Text;
34
using System;
45
using System.Collections.Generic;
6+
using System.Collections.Immutable;
57
using System.IO;
68
using System.Linq;
79
using 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($"\tpublic 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

Comments
 (0)