Skip to content

Commit

Permalink
Perf generators (#1357)
Browse files Browse the repository at this point in the history
* refactor: Use records for stub-generators

* refactor: Avoid ISymbols due to caching

* refactor: Centralize attribute generator
  • Loading branch information
linkdotnet authored Jan 26, 2024
1 parent 5f64044 commit 604e98d
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 121 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,18 @@ private static AddStubClassInfo GetStubClassInfo(GeneratorSyntaxContext context)
var line = lineSpan.StartLinePosition.Line + 1;
var column = lineSpan.Span.Start.Character + context.Node.ToString().IndexOf("AddStub", StringComparison.Ordinal) + 1;

var properties = symbol.GetMembers()
.OfType<IPropertySymbol>()
.Where(IsParameterOrCascadingParameter)
.Select(CreateFromProperty)
.ToImmutableArray();

return new AddStubClassInfo
{
StubClassName = $"{symbol.Name}Stub",
TargetTypeNamespace = symbol.ContainingNamespace.ToDisplayString(),
TargetType = symbol,
TargetTypeName = symbol.ToDisplayString(),
Properties = properties,
Path = path,
Line = line,
Column = column,
Expand All @@ -89,14 +96,31 @@ static string GetInterceptorFilePath(SyntaxTree tree, Compilation compilation)
{
return compilation.Options.SourceReferenceResolver?.NormalizePath(tree.FilePath, baseFilePath: null) ?? tree.FilePath;
}

static bool IsParameterOrCascadingParameter(ISymbol member)
{
return member.GetAttributes().Any(attr =>
attr.AttributeClass?.ToDisplayString() == "Microsoft.AspNetCore.Components.ParameterAttribute" ||
attr.AttributeClass?.ToDisplayString() == "Microsoft.AspNetCore.Components.CascadingParameterAttribute");
}

static StubPropertyInfo CreateFromProperty(IPropertySymbol member)
{
return new StubPropertyInfo
{
Name = member.Name,
Type = member.Type.ToDisplayString(),
AttributeLine = AttributeLineGenerator.GetAttributeLine(member),
};
}
}

private static void Execute(ImmutableArray<AddStubClassInfo> classInfos, SourceProductionContext context)
{
foreach (var stubClassGrouped in classInfos.GroupBy(c => c.UniqueQualifier))
{
var stubbedComponentGroup = stubClassGrouped.First();
var didStubComponent = StubComponentBuilder.GenerateStubComponent(stubbedComponentGroup, context);
var didStubComponent = GenerateStubComponent(stubbedComponentGroup, context);
if (didStubComponent)
{
GenerateInterceptorCode(stubbedComponentGroup, stubClassGrouped, context);
Expand Down Expand Up @@ -144,22 +168,65 @@ public InterceptsLocationAttribute(string filePath, int line, int column)
interceptorSource.AppendLine("\t\t\twhere TComponent : global::Microsoft.AspNetCore.Components.IComponent");
interceptorSource.AppendLine("\t\t{");
interceptorSource.AppendLine(
$"\t\t\treturn factories.Add<global::{stubbedComponentGroup.TargetType.ToDisplayString()}, global::{stubbedComponentGroup.TargetTypeNamespace}.{stubbedComponentGroup.StubClassName}>();");
$"\t\t\treturn factories.Add<global::{stubbedComponentGroup.TargetTypeName}, global::{stubbedComponentGroup.TargetTypeNamespace}.{stubbedComponentGroup.StubClassName}>();");
interceptorSource.AppendLine("\t\t}");
interceptorSource.AppendLine("\t}");
interceptorSource.AppendLine("}");

context.AddSource($"Interceptor{stubbedComponentGroup.StubClassName}.g.cs", interceptorSource.ToString());
}

private static bool GenerateStubComponent(AddStubClassInfo classInfo, SourceProductionContext context)
{
var hasSomethingToStub = false;
var sourceBuilder = new StringBuilder(1000);

sourceBuilder.AppendLine(HeaderProvider.Header);
sourceBuilder.AppendLine($"namespace {classInfo.TargetTypeNamespace};");
sourceBuilder.AppendLine();
sourceBuilder.AppendLine($"internal partial class {classInfo.StubClassName} : global::Microsoft.AspNetCore.Components.ComponentBase");
sourceBuilder.Append("{");

foreach (var member in classInfo.Properties)
{
sourceBuilder.AppendLine();

hasSomethingToStub = true;
var propertyType = member.Type;
var propertyName = member.Name;

var attributeLine = member.AttributeLine;
sourceBuilder.AppendLine(attributeLine);

sourceBuilder.AppendLine($"\tpublic {propertyType} {propertyName} {{ get; set; }} = default!;");
}

sourceBuilder.AppendLine("}");

if (hasSomethingToStub)
{
context.AddSource($"{classInfo.StubClassName}.g.cs", sourceBuilder.ToString());
}

return hasSomethingToStub;
}
}

internal sealed class AddStubClassInfo
internal sealed record AddStubClassInfo
{
public string StubClassName { get; set; }
public string TargetTypeNamespace { get; set; }
public string TargetTypeName { get; set; }
public string UniqueQualifier => $"{TargetTypeNamespace}.{StubClassName}";
public ITypeSymbol TargetType { get; set; }
public ImmutableArray<StubPropertyInfo> Properties { get; set; } = ImmutableArray<StubPropertyInfo>.Empty;
public string Path { get; set; }
public int Line { get; set; }
public int Column { get; set; }
}

internal sealed record StubPropertyInfo
{
public string Name { get; set; }
public string Type { get; set; }
public string AttributeLine { get; set; }
}

This file was deleted.

43 changes: 43 additions & 0 deletions src/bunit.generators/Web.Stubs/AttributeLineGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;

namespace Bunit.Web.Stubs;

internal static class AttributeLineGenerator
{
private const string CascadingParameterAttributeQualifier = "Microsoft.AspNetCore.Components.CascadingParameterAttribute";
private const string ParameterAttributeQualifier = "Microsoft.AspNetCore.Components.ParameterAttribute";

public static string GetAttributeLine(ISymbol member)
{
var attribute = member.GetAttributes().First(attr =>
attr.AttributeClass?.ToDisplayString() == ParameterAttributeQualifier ||
attr.AttributeClass?.ToDisplayString() == CascadingParameterAttributeQualifier);

var attributeLine = new StringBuilder("\t[");
if (attribute.AttributeClass?.ToDisplayString() == ParameterAttributeQualifier)
{
attributeLine.Append($"global::{ParameterAttributeQualifier}");
var captureUnmatchedValuesArg = attribute.NamedArguments
.FirstOrDefault(arg => arg.Key == "CaptureUnmatchedValues").Value;
if (captureUnmatchedValuesArg.Value is bool captureUnmatchedValues)
{
var captureString = captureUnmatchedValues ? "true" : "false";
attributeLine.Append($"(CaptureUnmatchedValues = {captureString})");
}
}
else if (attribute.AttributeClass?.ToDisplayString() == CascadingParameterAttributeQualifier)
{
attributeLine.Append($"global::{CascadingParameterAttributeQualifier}");
var nameArg = attribute.NamedArguments.FirstOrDefault(arg => arg.Key == "Name").Value;
if (!nameArg.IsNull)
{
attributeLine.Append($"(Name = \"{nameArg.Value}\")");
}
}

attributeLine.Append("]");
return attributeLine.ToString();
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
Expand Down Expand Up @@ -58,18 +59,42 @@ private static StubClassInfo GetStubClassInfo(GeneratorAttributeSyntaxContext co
continue;
}

var parameter = attribute.AttributeClass!.TypeArguments
.SelectMany(s => s.GetMembers())
.OfType<IPropertySymbol>()
.Where(IsParameterOrCascadingParameter)
.Select(CreateFromProperty)
.ToImmutableArray();

return new StubClassInfo
{
ClassName = className,
Namespace = namespaceName,
TargetType = originalTypeToStub,
Visibility = visibility,
IsNestedClass = context.TargetSymbol.ContainingType is not null,
IsPartial = isPartial,
Properties = parameter,
};
}

return null;

static bool IsParameterOrCascadingParameter(ISymbol member)
{
return member.GetAttributes().Any(attr =>
attr.AttributeClass?.ToDisplayString() == "Microsoft.AspNetCore.Components.ParameterAttribute" ||
attr.AttributeClass?.ToDisplayString() == "Microsoft.AspNetCore.Components.CascadingParameterAttribute");
}

static StubPropertyInfo CreateFromProperty(IPropertySymbol member)
{
return new StubPropertyInfo
{
Name = member.Name,
Type = member.Type.ToDisplayString(),
AttributeLine = AttributeLineGenerator.GetAttributeLine(member),
};
}
}

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

var hasSomethingToStub = false;
var targetTypeSymbol = (INamedTypeSymbol)classInfo!.TargetType;
var sourceBuilder = new StringBuilder(1000);

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

foreach (var member in targetTypeSymbol
.GetMembers()
.OfType<IPropertySymbol>()
.Where(p => p.GetAttributes()
.Any(attr =>
attr.AttributeClass?.ToDisplayString() ==
"Microsoft.AspNetCore.Components.ParameterAttribute" ||
attr.AttributeClass?.ToDisplayString() ==
"Microsoft.AspNetCore.Components.CascadingParameterAttribute")))
foreach (var member in classInfo.Properties)
{
sourceBuilder.AppendLine();

hasSomethingToStub = true;
var propertyType = member.Type.ToDisplayString();
var propertyType = member.Type;
var propertyName = member.Name;

var attributeLine = GetAttributeLineForMember(member);

sourceBuilder.AppendLine(attributeLine);
sourceBuilder.AppendLine(member.AttributeLine);
sourceBuilder.AppendLine($"\tpublic {propertyType} {propertyName} {{ get; set; }} = default!;");
}

Expand All @@ -118,16 +132,6 @@ private static void Execute(StubClassInfo classInfo, SourceProductionContext con
{
context.AddSource($"{classInfo.ClassName}.g.cs", sourceBuilder.ToString());
}

static string GetAttributeLineForMember(ISymbol member)
{
var isParameterAttribute = member.GetAttributes().Any(attr =>
attr.AttributeClass?.ToDisplayString() == "Microsoft.AspNetCore.Components.ParameterAttribute");
var attributeLine = isParameterAttribute
? "\t[global::Microsoft.AspNetCore.Components.Parameter]"
: "\t[global::Microsoft.AspNetCore.Components.CascadingParameter]";
return attributeLine;
}
}

private static bool CheckDiagnostics(StubClassInfo classInfo, SourceProductionContext context)
Expand All @@ -144,7 +148,7 @@ private static bool CheckDiagnostics(StubClassInfo classInfo, SourceProductionCo
isEnabledByDefault: true,
helpLinkUri: helpUrl),
Location.None,
classInfo.TargetType.ToDisplayString()));
classInfo.ClassName));
return true;
}

Expand All @@ -158,20 +162,28 @@ private static bool CheckDiagnostics(StubClassInfo classInfo, SourceProductionCo
isEnabledByDefault: true,
helpLinkUri: helpUrl),
Location.None,
classInfo.TargetType.ToDisplayString()));
classInfo.ClassName));
return true;
}

return false;
}
}

internal sealed class StubClassInfo
internal sealed record StubClassInfo
{
public string ClassName { get; set; }
public string Namespace { get; set; }
public ITypeSymbol TargetType { get; set; }
public ImmutableArray<StubPropertyInfo> Properties { get; set; } = ImmutableArray<StubPropertyInfo>.Empty;
public string Visibility { get; set; }
public bool IsNestedClass { get; set; }
public bool IsPartial { get; set; }
}

internal sealed record StubPropertyInfo
{
public string Name { get; set; }
public string Type { get; set; }
public string AttributeLine { get; set; }
}

Loading

0 comments on commit 604e98d

Please sign in to comment.