Skip to content

Commit 604e98d

Browse files
authored
Perf generators (#1357)
* refactor: Use records for stub-generators * refactor: Avoid ISymbols due to caching * refactor: Centralize attribute generator
1 parent 5f64044 commit 604e98d

File tree

5 files changed

+157
-121
lines changed

5 files changed

+157
-121
lines changed

src/bunit.generators/Web.Stubs/AddStubMethodStubGenerator/AddStubGenerator.cs

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,18 @@ private static AddStubClassInfo GetStubClassInfo(GeneratorSyntaxContext context)
5959
var line = lineSpan.StartLinePosition.Line + 1;
6060
var column = lineSpan.Span.Start.Character + context.Node.ToString().IndexOf("AddStub", StringComparison.Ordinal) + 1;
6161

62+
var properties = symbol.GetMembers()
63+
.OfType<IPropertySymbol>()
64+
.Where(IsParameterOrCascadingParameter)
65+
.Select(CreateFromProperty)
66+
.ToImmutableArray();
67+
6268
return new AddStubClassInfo
6369
{
6470
StubClassName = $"{symbol.Name}Stub",
6571
TargetTypeNamespace = symbol.ContainingNamespace.ToDisplayString(),
66-
TargetType = symbol,
72+
TargetTypeName = symbol.ToDisplayString(),
73+
Properties = properties,
6774
Path = path,
6875
Line = line,
6976
Column = column,
@@ -89,14 +96,31 @@ static string GetInterceptorFilePath(SyntaxTree tree, Compilation compilation)
8996
{
9097
return compilation.Options.SourceReferenceResolver?.NormalizePath(tree.FilePath, baseFilePath: null) ?? tree.FilePath;
9198
}
99+
100+
static bool IsParameterOrCascadingParameter(ISymbol member)
101+
{
102+
return member.GetAttributes().Any(attr =>
103+
attr.AttributeClass?.ToDisplayString() == "Microsoft.AspNetCore.Components.ParameterAttribute" ||
104+
attr.AttributeClass?.ToDisplayString() == "Microsoft.AspNetCore.Components.CascadingParameterAttribute");
105+
}
106+
107+
static StubPropertyInfo CreateFromProperty(IPropertySymbol member)
108+
{
109+
return new StubPropertyInfo
110+
{
111+
Name = member.Name,
112+
Type = member.Type.ToDisplayString(),
113+
AttributeLine = AttributeLineGenerator.GetAttributeLine(member),
114+
};
115+
}
92116
}
93117

94118
private static void Execute(ImmutableArray<AddStubClassInfo> classInfos, SourceProductionContext context)
95119
{
96120
foreach (var stubClassGrouped in classInfos.GroupBy(c => c.UniqueQualifier))
97121
{
98122
var stubbedComponentGroup = stubClassGrouped.First();
99-
var didStubComponent = StubComponentBuilder.GenerateStubComponent(stubbedComponentGroup, context);
123+
var didStubComponent = GenerateStubComponent(stubbedComponentGroup, context);
100124
if (didStubComponent)
101125
{
102126
GenerateInterceptorCode(stubbedComponentGroup, stubClassGrouped, context);
@@ -144,22 +168,65 @@ public InterceptsLocationAttribute(string filePath, int line, int column)
144168
interceptorSource.AppendLine("\t\t\twhere TComponent : global::Microsoft.AspNetCore.Components.IComponent");
145169
interceptorSource.AppendLine("\t\t{");
146170
interceptorSource.AppendLine(
147-
$"\t\t\treturn factories.Add<global::{stubbedComponentGroup.TargetType.ToDisplayString()}, global::{stubbedComponentGroup.TargetTypeNamespace}.{stubbedComponentGroup.StubClassName}>();");
171+
$"\t\t\treturn factories.Add<global::{stubbedComponentGroup.TargetTypeName}, global::{stubbedComponentGroup.TargetTypeNamespace}.{stubbedComponentGroup.StubClassName}>();");
148172
interceptorSource.AppendLine("\t\t}");
149173
interceptorSource.AppendLine("\t}");
150174
interceptorSource.AppendLine("}");
151175

152176
context.AddSource($"Interceptor{stubbedComponentGroup.StubClassName}.g.cs", interceptorSource.ToString());
153177
}
178+
179+
private static bool GenerateStubComponent(AddStubClassInfo classInfo, SourceProductionContext context)
180+
{
181+
var hasSomethingToStub = false;
182+
var sourceBuilder = new StringBuilder(1000);
183+
184+
sourceBuilder.AppendLine(HeaderProvider.Header);
185+
sourceBuilder.AppendLine($"namespace {classInfo.TargetTypeNamespace};");
186+
sourceBuilder.AppendLine();
187+
sourceBuilder.AppendLine($"internal partial class {classInfo.StubClassName} : global::Microsoft.AspNetCore.Components.ComponentBase");
188+
sourceBuilder.Append("{");
189+
190+
foreach (var member in classInfo.Properties)
191+
{
192+
sourceBuilder.AppendLine();
193+
194+
hasSomethingToStub = true;
195+
var propertyType = member.Type;
196+
var propertyName = member.Name;
197+
198+
var attributeLine = member.AttributeLine;
199+
sourceBuilder.AppendLine(attributeLine);
200+
201+
sourceBuilder.AppendLine($"\tpublic {propertyType} {propertyName} {{ get; set; }} = default!;");
202+
}
203+
204+
sourceBuilder.AppendLine("}");
205+
206+
if (hasSomethingToStub)
207+
{
208+
context.AddSource($"{classInfo.StubClassName}.g.cs", sourceBuilder.ToString());
209+
}
210+
211+
return hasSomethingToStub;
212+
}
154213
}
155214

156-
internal sealed class AddStubClassInfo
215+
internal sealed record AddStubClassInfo
157216
{
158217
public string StubClassName { get; set; }
159218
public string TargetTypeNamespace { get; set; }
219+
public string TargetTypeName { get; set; }
160220
public string UniqueQualifier => $"{TargetTypeNamespace}.{StubClassName}";
161-
public ITypeSymbol TargetType { get; set; }
221+
public ImmutableArray<StubPropertyInfo> Properties { get; set; } = ImmutableArray<StubPropertyInfo>.Empty;
162222
public string Path { get; set; }
163223
public int Line { get; set; }
164224
public int Column { get; set; }
165225
}
226+
227+
internal sealed record StubPropertyInfo
228+
{
229+
public string Name { get; set; }
230+
public string Type { get; set; }
231+
public string AttributeLine { get; set; }
232+
}

src/bunit.generators/Web.Stubs/AddStubMethodStubGenerator/StubComponentBuilder.cs

Lines changed: 0 additions & 87 deletions
This file was deleted.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System.Linq;
2+
using System.Text;
3+
using Microsoft.CodeAnalysis;
4+
5+
namespace Bunit.Web.Stubs;
6+
7+
internal static class AttributeLineGenerator
8+
{
9+
private const string CascadingParameterAttributeQualifier = "Microsoft.AspNetCore.Components.CascadingParameterAttribute";
10+
private const string ParameterAttributeQualifier = "Microsoft.AspNetCore.Components.ParameterAttribute";
11+
12+
public static string GetAttributeLine(ISymbol member)
13+
{
14+
var attribute = member.GetAttributes().First(attr =>
15+
attr.AttributeClass?.ToDisplayString() == ParameterAttributeQualifier ||
16+
attr.AttributeClass?.ToDisplayString() == CascadingParameterAttributeQualifier);
17+
18+
var attributeLine = new StringBuilder("\t[");
19+
if (attribute.AttributeClass?.ToDisplayString() == ParameterAttributeQualifier)
20+
{
21+
attributeLine.Append($"global::{ParameterAttributeQualifier}");
22+
var captureUnmatchedValuesArg = attribute.NamedArguments
23+
.FirstOrDefault(arg => arg.Key == "CaptureUnmatchedValues").Value;
24+
if (captureUnmatchedValuesArg.Value is bool captureUnmatchedValues)
25+
{
26+
var captureString = captureUnmatchedValues ? "true" : "false";
27+
attributeLine.Append($"(CaptureUnmatchedValues = {captureString})");
28+
}
29+
}
30+
else if (attribute.AttributeClass?.ToDisplayString() == CascadingParameterAttributeQualifier)
31+
{
32+
attributeLine.Append($"global::{CascadingParameterAttributeQualifier}");
33+
var nameArg = attribute.NamedArguments.FirstOrDefault(arg => arg.Key == "Name").Value;
34+
if (!nameArg.IsNull)
35+
{
36+
attributeLine.Append($"(Name = \"{nameArg.Value}\")");
37+
}
38+
}
39+
40+
attributeLine.Append("]");
41+
return attributeLine.ToString();
42+
}
43+
}

src/bunit.generators/Web.Stubs/AttributeStubGenerator/ComponentStubAttributeGenerator.cs

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Collections.Immutable;
12
using System.Linq;
23
using System.Text;
34
using Microsoft.CodeAnalysis;
@@ -58,18 +59,42 @@ private static StubClassInfo GetStubClassInfo(GeneratorAttributeSyntaxContext co
5859
continue;
5960
}
6061

62+
var parameter = attribute.AttributeClass!.TypeArguments
63+
.SelectMany(s => s.GetMembers())
64+
.OfType<IPropertySymbol>()
65+
.Where(IsParameterOrCascadingParameter)
66+
.Select(CreateFromProperty)
67+
.ToImmutableArray();
68+
6169
return new StubClassInfo
6270
{
6371
ClassName = className,
6472
Namespace = namespaceName,
65-
TargetType = originalTypeToStub,
6673
Visibility = visibility,
6774
IsNestedClass = context.TargetSymbol.ContainingType is not null,
6875
IsPartial = isPartial,
76+
Properties = parameter,
6977
};
7078
}
7179

7280
return null;
81+
82+
static bool IsParameterOrCascadingParameter(ISymbol member)
83+
{
84+
return member.GetAttributes().Any(attr =>
85+
attr.AttributeClass?.ToDisplayString() == "Microsoft.AspNetCore.Components.ParameterAttribute" ||
86+
attr.AttributeClass?.ToDisplayString() == "Microsoft.AspNetCore.Components.CascadingParameterAttribute");
87+
}
88+
89+
static StubPropertyInfo CreateFromProperty(IPropertySymbol member)
90+
{
91+
return new StubPropertyInfo
92+
{
93+
Name = member.Name,
94+
Type = member.Type.ToDisplayString(),
95+
AttributeLine = AttributeLineGenerator.GetAttributeLine(member),
96+
};
97+
}
7398
}
7499

75100
private static void Execute(StubClassInfo classInfo, SourceProductionContext context)
@@ -80,7 +105,6 @@ private static void Execute(StubClassInfo classInfo, SourceProductionContext con
80105
}
81106

82107
var hasSomethingToStub = false;
83-
var targetTypeSymbol = (INamedTypeSymbol)classInfo!.TargetType;
84108
var sourceBuilder = new StringBuilder(1000);
85109

86110
sourceBuilder.AppendLine(HeaderProvider.Header);
@@ -90,25 +114,15 @@ private static void Execute(StubClassInfo classInfo, SourceProductionContext con
90114
$"{classInfo.Visibility} partial class {classInfo.ClassName} : global::Microsoft.AspNetCore.Components.ComponentBase");
91115
sourceBuilder.Append("{");
92116

93-
foreach (var member in targetTypeSymbol
94-
.GetMembers()
95-
.OfType<IPropertySymbol>()
96-
.Where(p => p.GetAttributes()
97-
.Any(attr =>
98-
attr.AttributeClass?.ToDisplayString() ==
99-
"Microsoft.AspNetCore.Components.ParameterAttribute" ||
100-
attr.AttributeClass?.ToDisplayString() ==
101-
"Microsoft.AspNetCore.Components.CascadingParameterAttribute")))
117+
foreach (var member in classInfo.Properties)
102118
{
103119
sourceBuilder.AppendLine();
104120

105121
hasSomethingToStub = true;
106-
var propertyType = member.Type.ToDisplayString();
122+
var propertyType = member.Type;
107123
var propertyName = member.Name;
108124

109-
var attributeLine = GetAttributeLineForMember(member);
110-
111-
sourceBuilder.AppendLine(attributeLine);
125+
sourceBuilder.AppendLine(member.AttributeLine);
112126
sourceBuilder.AppendLine($"\tpublic {propertyType} {propertyName} {{ get; set; }} = default!;");
113127
}
114128

@@ -118,16 +132,6 @@ private static void Execute(StubClassInfo classInfo, SourceProductionContext con
118132
{
119133
context.AddSource($"{classInfo.ClassName}.g.cs", sourceBuilder.ToString());
120134
}
121-
122-
static string GetAttributeLineForMember(ISymbol member)
123-
{
124-
var isParameterAttribute = member.GetAttributes().Any(attr =>
125-
attr.AttributeClass?.ToDisplayString() == "Microsoft.AspNetCore.Components.ParameterAttribute");
126-
var attributeLine = isParameterAttribute
127-
? "\t[global::Microsoft.AspNetCore.Components.Parameter]"
128-
: "\t[global::Microsoft.AspNetCore.Components.CascadingParameter]";
129-
return attributeLine;
130-
}
131135
}
132136

133137
private static bool CheckDiagnostics(StubClassInfo classInfo, SourceProductionContext context)
@@ -144,7 +148,7 @@ private static bool CheckDiagnostics(StubClassInfo classInfo, SourceProductionCo
144148
isEnabledByDefault: true,
145149
helpLinkUri: helpUrl),
146150
Location.None,
147-
classInfo.TargetType.ToDisplayString()));
151+
classInfo.ClassName));
148152
return true;
149153
}
150154

@@ -158,20 +162,28 @@ private static bool CheckDiagnostics(StubClassInfo classInfo, SourceProductionCo
158162
isEnabledByDefault: true,
159163
helpLinkUri: helpUrl),
160164
Location.None,
161-
classInfo.TargetType.ToDisplayString()));
165+
classInfo.ClassName));
162166
return true;
163167
}
164168

165169
return false;
166170
}
167171
}
168172

169-
internal sealed class StubClassInfo
173+
internal sealed record StubClassInfo
170174
{
171175
public string ClassName { get; set; }
172176
public string Namespace { get; set; }
173-
public ITypeSymbol TargetType { get; set; }
177+
public ImmutableArray<StubPropertyInfo> Properties { get; set; } = ImmutableArray<StubPropertyInfo>.Empty;
174178
public string Visibility { get; set; }
175179
public bool IsNestedClass { get; set; }
176180
public bool IsPartial { get; set; }
177181
}
182+
183+
internal sealed record StubPropertyInfo
184+
{
185+
public string Name { get; set; }
186+
public string Type { get; set; }
187+
public string AttributeLine { get; set; }
188+
}
189+

0 commit comments

Comments
 (0)