Skip to content

Commit

Permalink
Create StringWithTypes and use it as statement or expression
Browse files Browse the repository at this point in the history
* Add `StringWithTypes`
* Add interpolated string handler that create `StringWithTypes`
* Use as initialize in `ParameterBuilder`, `FieldBuilder` -> `PropertyBuilder`
* Use as body statement in `MethodLikeBuilder` -> `MethodBuilder`, `ConstructorBuilder`
* Update usage
  • Loading branch information
YarinOmesi committed Aug 5, 2023
1 parent 4c581f4 commit 4eb1c02
Show file tree
Hide file tree
Showing 14 changed files with 155 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public override MemberDeclarationSyntax Build(BuildContext context)
return SyntaxFactory.ConstructorDeclaration(SyntaxFactory.Identifier(_typeBuilder.Name))
.WithModifiers(BuildModifiers())
.WithParameterList(BuildParameters(context))
.WithBody(BuildBody());
.WithBody(BuildBody(context));
}

public static ConstructorBuilder CreateAndAdd(TypeBuilder type, params ParameterBuilder[] parameters)
Expand Down
2 changes: 1 addition & 1 deletion TestsHelper.SourceGenerator/CodeBuilding/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ string parameterName
{
ParameterBuilder parameterBuilder = ParameterBuilder.Create(field.Type, parameterName);
builder.AddParameters(parameterBuilder);
builder.AddBodyStatements(field.Assign(parameterName));
builder.AddBodyStatement($"{field} = {parameterName};");
return parameterBuilder;
}

Expand Down
13 changes: 8 additions & 5 deletions TestsHelper.SourceGenerator/CodeBuilding/FieldBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,23 @@ public class FieldBuilder : MemberBuilder
{
public string Name { get; set; } = null!;
public IType Type { get; set; } = null!;
public string? Initializer { get; set; } = null;
public StringWithTypes Initializer { get; set; } = StringWithTypes.Empty;

protected FieldBuilder() { }

protected EqualsValueClauseSyntax? BuildInitializer() => Initializer == null ? null : EqualsValueClause(ParseExpression(Initializer));
protected EqualsValueClauseSyntax? BuildInitializer(BuildContext context)
{
return Initializer.IsEmpty ? null : EqualsValueClause(ParseExpression(Initializer.ToString(context.FileBuilder)));
}

public override MemberDeclarationSyntax Build(BuildContext context)
{
TypeSyntax type = Type.TryRegisterAlias(context.FileBuilder).Build();

return FieldDeclaration(VariableDeclaration(type)
.AddVariables(VariableDeclarator(Name).WithInitializer(BuildInitializer()))).WithModifiers(BuildModifiers());
.AddVariables(VariableDeclarator(Name).WithInitializer(BuildInitializer(context)))).WithModifiers(BuildModifiers());
}

public static FieldBuilder Create(IType type, string name, string? initializer = null) =>
new() {Type = type, Name = name, Initializer = initializer};
public static FieldBuilder Create(IType type, string name, StringWithTypes? initializer = null) =>
new() {Type = type, Name = name, Initializer = initializer ?? StringWithTypes.Empty};
}
2 changes: 1 addition & 1 deletion TestsHelper.SourceGenerator/CodeBuilding/MethodBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public override MemberDeclarationSyntax Build(BuildContext context)

return SyntaxFactory.MethodDeclaration(returnType, identifier: SyntaxFactory.Identifier(Name))
.WithModifiers(BuildModifiers())
.WithBody(BuildBody())
.WithBody(BuildBody(context))
.WithParameterList(BuildParameters(context));
}

Expand Down
15 changes: 9 additions & 6 deletions TestsHelper.SourceGenerator/CodeBuilding/MethodLikeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,21 @@ namespace TestsHelper.SourceGenerator.CodeBuilding;

public abstract class MethodLikeBuilder : MemberBuilder
{
public IReadOnlyList<ParameterBuilder> Parameters => _parameters;
private List<string> Body { get; } = new List<string>();
private List<StringWithTypes> Body { get; } = new List<StringWithTypes>();

private readonly List<ParameterBuilder> _parameters = new();

public void AddParameters(params ParameterBuilder[] parameterBuilders) => _parameters.AddRange(parameterBuilders);
public void AddBodyStatements(params string[] statements) => Body.AddRange(statements);

protected BlockSyntax BuildBody() => SyntaxFactory.Block(Body.Select(s=> SyntaxFactory.ParseStatement(s)));
public void AddBodyStatement(StringWithTypesInterpolatedStringHandler stringHandler) => Body.Add(stringHandler.StringWithTypes);

protected BlockSyntax BuildBody(BuildContext context)
{
return SyntaxFactory.Block(Body
.Select(stringWithTypes => stringWithTypes.ToString(context.FileBuilder))
.Select(static s => SyntaxFactory.ParseStatement(s)));
}
protected ParameterListSyntax BuildParameters(BuildContext context)
{
return SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList<ParameterSyntax>(Parameters.Select(builder => builder.Build(context))));
return SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList<ParameterSyntax>(_parameters.Select(builder => builder.Build(context))));
}
}
12 changes: 6 additions & 6 deletions TestsHelper.SourceGenerator/CodeBuilding/ParameterBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using TestsHelper.SourceGenerator.CodeBuilding.Types;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace TestsHelper.SourceGenerator.CodeBuilding;

public class ParameterBuilder
{
public string Name { get; set; } = null!;
public IType Type { get; set; } = null!;
public string? Initializer { get; set; } = null;
public StringWithTypes Initializer { get; set; } = StringWithTypes.Empty;

private ParameterBuilder(){}

public ParameterSyntax Build(BuildContext context)
{
TypeSyntax type = Type.TryRegisterAlias(context.FileBuilder).Build();

return SyntaxFactory.Parameter(SyntaxFactory.Identifier(Name))
return Parameter(Identifier(Name))
.WithType(type)
.WithDefault(Initializer == null ? null : SyntaxFactory.EqualsValueClause(SyntaxFactory.ParseExpression(Initializer)));
.WithDefault(Initializer.IsEmpty ? null : EqualsValueClause(ParseExpression(Initializer.ToString(context.FileBuilder))));
}

public static ParameterBuilder Create(IType type, string name, string? initializer = null) =>
new() {Type = type, Name = name, Initializer = initializer};
public static ParameterBuilder Create(IType type, string name, StringWithTypes? initializer = null) =>
new() {Type = type, Name = name, Initializer = initializer ?? StringWithTypes.Empty};
}
6 changes: 3 additions & 3 deletions TestsHelper.SourceGenerator/CodeBuilding/PropertyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public override MemberDeclarationSyntax Build(BuildContext context)

PropertyDeclarationSyntax syntax = SyntaxFactory.PropertyDeclaration(type, Name)
.WithModifiers(BuildModifiers())
.WithInitializer(BuildInitializer());
.WithInitializer(BuildInitializer(context));

if (AutoSetter)
syntax = AddAccessor(syntax, SyntaxKind.SetAccessorDeclaration);
Expand All @@ -34,11 +34,11 @@ public override MemberDeclarationSyntax Build(BuildContext context)
}

[Pure]
public static PropertyBuilder Create(IType type, string name, string? initializer = null, bool autoGetter = false,
public static PropertyBuilder Create(IType type, string name, StringWithTypes? initializer = null, bool autoGetter = false,
bool autoSetter = false)
{
return new PropertyBuilder() {
Type = type, Name = name, Initializer = initializer, AutoGetter = autoGetter, AutoSetter = autoSetter
Type = type, Name = name, Initializer = initializer?? StringWithTypes.Empty, AutoGetter = autoGetter, AutoSetter = autoSetter
};
}
}
63 changes: 63 additions & 0 deletions TestsHelper.SourceGenerator/CodeBuilding/StringWithTypes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OneOf;
using TestsHelper.SourceGenerator.CodeBuilding.Types;

namespace TestsHelper.SourceGenerator.CodeBuilding;

public sealed class StringWithTypes
{
public static readonly StringWithTypes Empty = new(EmptyList<OneOf<string, IType, StringWithTypes, MultipleValues<StringWithTypes>>>.Instance);

public bool IsEmpty => this == Empty;

private readonly List<OneOf<string, IType, StringWithTypes, MultipleValues<StringWithTypes>>> _components;

private StringWithTypes(List<OneOf<string, IType, StringWithTypes, MultipleValues<StringWithTypes>>> components)
{
_components = components;
}

public void Add(OneOf<string, IType, StringWithTypes, MultipleValues<StringWithTypes>> a) => _components.Add(a);
public StringWithTypes TakeIf(bool condition) => condition ? this : Empty;

public string ToString(FileBuilder fileBuilder)
{
StringBuilder builder = new StringBuilder();

foreach (OneOf<string, IType, StringWithTypes, MultipleValues<StringWithTypes>> oneOf in _components)
{
string? text = oneOf.Match(
static s => s,
type => type.TryRegisterAlias(fileBuilder).MakeString(),
stringWithTypes => stringWithTypes.IsEmpty ? null : stringWithTypes.ToString(fileBuilder),
multipleStrings =>
string.Join(multipleStrings.Separator, multipleStrings.Values
.Where(types => !types.IsEmpty)
.Select(types => types.ToString(fileBuilder))
)
);
if (text != null)
{
builder.Append(text);
}
}

return builder.ToString();
}

public static StringWithTypes Format(StringWithTypesInterpolatedStringHandler stringHandler) => stringHandler.StringWithTypes;

public static StringWithTypes Create(int capacity) =>
new(new List<OneOf<string, IType, StringWithTypes, MultipleValues<StringWithTypes>>>(capacity));

public readonly record struct MultipleValues<T>(T[] Values, string Separator);

public static implicit operator StringWithTypes(string s)
{
StringWithTypes stringWithTypes = Create(1);
stringWithTypes.Add(s);
return stringWithTypes;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using TestsHelper.SourceGenerator.CodeBuilding.Types;

namespace TestsHelper.SourceGenerator.CodeBuilding;

[InterpolatedStringHandler]
public readonly ref struct StringWithTypesInterpolatedStringHandler
{
public StringWithTypes StringWithTypes { get; }

public StringWithTypesInterpolatedStringHandler(int literalLength, int formattedCount)
{
StringWithTypes = StringWithTypes.Create(literalLength + formattedCount);
}

public void AppendLiteral(string s) => StringWithTypes.Add(s);

public void AppendFormatted<T>(T t) where T : IType => StringWithTypes.Add(t);

public void AppendFormatted(FieldBuilder field) => StringWithTypes.Add(field.Name);

public void AppendFormatted(ParameterBuilder parameterBuilder) => StringWithTypes.Add(parameterBuilder.Name);

public void AppendFormatted(TypeBuilder typeBuilder) => StringWithTypes.Add(typeBuilder.Name);

public void AppendFormatted(StringWithTypes stringWithTypes) => StringWithTypes.Add(stringWithTypes);

public void AppendFormatted(IEnumerable<string> array, string format) => StringWithTypes.Add(string.Join(format, array));

public void AppendFormatted(IEnumerable<StringWithTypes> stringWithTypes, string format)
{
StringWithTypes.Add(new StringWithTypes.MultipleValues<StringWithTypes>(stringWithTypes.ToArray(), format));
}

public void AppendFormatted(string s) => StringWithTypes.Add(s);

internal string GetFormattedText() => string.Empty;
}
8 changes: 0 additions & 8 deletions TestsHelper.SourceGenerator/Extensions.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,10 @@ public void Generate(TypeBuilder builder, IType dependencyTypeName, IMethodSymbo
? CommonTypes.SystemAction.Generic(dependencyTypeName)
: CommonTypes.SystemFunc.Generic(dependencyTypeName, method.ReturnType.Type());


IEnumerable<IType> methodParameter = method.Parameters.Select(symbol => symbol.Type.Type().TryRegisterAlias(builder.ParentFileBuilder));

FieldBuilder expressionField = FieldBuilder.Create(
CommonTypes.LinqExpression.Generic(moqCallbackType),
"_expression",
CreateMoqExpressionLambda("p", method.Name ,methodParameter)
CreateMoqExpressionLambda("p", method)
).Add(builder).Private().Readonly();

FieldBuilder mockField = FieldBuilder.Create(Moq.Mock.Generic(dependencyTypeName), "_mock").Add(builder);
Expand All @@ -45,45 +42,40 @@ public void Generate(TypeBuilder builder, IType dependencyTypeName, IMethodSymbo
))
.ToArray();

string patchedExpression = Cyber_CretePatchedExpression(method, expressionField.Name, converterField.Name);
StringWithTypes patchedExpression = Cyber_CretePatchedExpression(method, expressionField.Name, converterField.Name);

// Setup()
var setupReturnType = Moq.ISetup with {TypedArguments = moqCallbackType.TypedArguments};

var setupBuilder = MethodBuilder.Create(setupReturnType, "Setup", parameters).Add(builder)
.Public();
setupBuilder.AddBodyStatements(
$"var expression = {patchedExpression};",
$"return {mockField.Name}.Setup(expression);"
);
setupBuilder.AddBodyStatement($"var expression = {patchedExpression};");
setupBuilder.AddBodyStatement($"return {mockField}.Setup(expression);");

// Verify()
var verifyBuilder = MethodBuilder.Create(VoidType.Instance, "Verify", parameters).Add(builder)
.Public();
ParameterBuilder timesParameter = ParameterBuilder.Create(Moq.Times.Nullable(), "times", "null");
verifyBuilder.AddParameters(timesParameter);

string timesTypeName = Moq.Times.TryRegisterAlias(builder.ParentFileBuilder).Name;
verifyBuilder.AddBodyStatements(
$"var expression = {patchedExpression};",
$"{mockField.Name}.Verify(expression, {timesParameter.Name} ?? {timesTypeName}.AtLeastOnce());"
);
verifyBuilder.AddBodyStatement($"var expression = {patchedExpression};");
verifyBuilder.AddBodyStatement($"{mockField}.Verify(expression, {timesParameter} ?? {Moq.Times}.AtLeastOnce());");
}

private static string CreateMoqExpressionLambda(string parameterName,string methodName, IEnumerable<IType> method)
private static StringWithTypes CreateMoqExpressionLambda(string parameterName, IMethodSymbol method)
{
IEnumerable<string> allParameterTypesFilled = method.Select(parameter => Cyber_Fill(parameter.Name));
IEnumerable<IType> methodParameters = method.Parameters.Select(symbol => symbol.Type.Type());

return $"{parameterName} => {parameterName}.{methodName}({allParameterTypesFilled.JoinToString(", ")})";
return StringWithTypes.Format($"{parameterName} => {parameterName}.{method.Name}({methodParameters.Select(Cyber_Fill):,})");
}

private static string Cyber_CretePatchedExpression(IMethodSymbol method, string variableName, string converterFieldName)
private static StringWithTypes Cyber_CretePatchedExpression(IMethodSymbol method, string variableName, string converterFieldName)
{
IEnumerable<string> converterParameters = method.Parameters.Select(parameter => $"{converterFieldName}.Convert({parameter.Name})");
IEnumerable<string> parameters = converterParameters.Prepend(variableName);

return $"Cyber.UpdateExpressionWithParameters({parameters.JoinToString(", ")})";
return StringWithTypes.Format($"Cyber.UpdateExpressionWithParameters({parameters:,})");
}

private static string Cyber_Fill(string type) => $"Cyber.Fill<{type}>()";
private static StringWithTypes Cyber_Fill(IType type) => StringWithTypes.Format($"Cyber.Fill<{type}>()");
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@ private void CreateMethodWrappers(TypeBuilder builder, ITypeSymbol dependencyTyp
TypeBuilder methodWrapperClass = builder.AddClass();
_dependencyMethodWrapperClassGenerator.Generate(methodWrapperClass, dependencyType.Type(), method);

IType type = methodWrapperClass.Type().TryRegisterAlias(builder.ParentFileBuilder);
IType type = methodWrapperClass.Type();
PropertyBuilder methodProperty = PropertyBuilder.Create(type, name, autoGetter: true).Add(builder)
.Public();

constructorBuilder.AddBodyStatements(methodProperty.Assign(type.New(methodWrapperClassParameters)));
constructorBuilder.AddBodyStatement($"{methodProperty} = new {type}({methodWrapperClassParameters:,});");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,11 @@ IType testedClassType

Dictionary<string, string> parameterNameToFieldInitializer = new Dictionary<string, string>();

IType returnType = testedClassType.TryRegisterAlias(partialClassBuilder.ParentFileBuilder);
MethodBuilder buildMethodBuilder = MethodBuilder.Create(returnType, "Build").Add(partialClassBuilder)
MethodBuilder buildMethodBuilder = MethodBuilder.Create(testedClassType, "Build").Add(partialClassBuilder)
.Private();
if (generationMode == WrapperGenerationMode.MethodsWrap)
{
buildMethodBuilder.AddBodyStatements($"var converter = {CommonTypes.MoqValueConverter.MakeString()}.Instance;");
buildMethodBuilder.AddBodyStatement($"var converter = {CommonTypes.MoqValueConverter}.Instance;");
}

foreach (string parameterName in dependencyBehaviors.Keys)
Expand All @@ -62,15 +61,16 @@ IType testedClassType
TypeBuilder dependencyWrapperType = wrapperFile.AddClass();
dependencyWrapperGenerator.GenerateCode(dependencyWrapperType, mockDependencyBehavior.Type);

IType type = dependencyWrapperType.Type().TryRegisterAlias(partialClassBuilder.ParentFileBuilder);
FieldBuilder dependencyWrapperField = FieldBuilder.Create(type, $"_{parameterName}")
FieldBuilder dependencyWrapperField = FieldBuilder.Create(dependencyWrapperType.Type(), $"_{parameterName}")
.Add(partialClassBuilder)
.Private();

List<string> parameters = new() {Moq.Mock.Generic(mockDependencyBehavior.Type).New()};
if(generationMode == WrapperGenerationMode.MethodsWrap) parameters.Add("converter");
StringWithTypes[] parameters = new StringWithTypes[] {
StringWithTypes.Format($"new {Moq.Mock.Generic(mockDependencyBehavior.Type)}()"),
StringWithTypes.Format($"converter").TakeIf(generationMode == WrapperGenerationMode.MethodsWrap)
};

buildMethodBuilder.AddBodyStatements(dependencyWrapperField.Assign(type.New(parameters.ToArray())));
buildMethodBuilder.AddBodyStatement($"{dependencyWrapperField} = new {dependencyWrapperType}({parameters:,});");

// TODO: remove coupling from moq
parameterNameToFieldInitializer[parameterName] = $"{dependencyWrapperField.Name}.Mock.Object";
Expand All @@ -86,7 +86,7 @@ IType testedClassType
.Select(parameter => parameterNameToFieldInitializer[parameter.Name])
.ToArray();

buildMethodBuilder.AddBodyStatements(returnType.New(arguments).Return());
buildMethodBuilder.AddBodyStatement($"return new {testedClassType}({arguments:,});");

return fileBuilders;
}
Expand Down
Loading

0 comments on commit 4eb1c02

Please sign in to comment.