Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions Source/Mockolate.SourceGenerators/Entities/MockClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,4 @@ public MockClass(ITypeSymbol[] types) : base(types[0])

public IEnumerable<Class> DistinctAdditionalImplementations()
=> AdditionalImplementations.Distinct().Where(x => x.ClassFullName != ClassFullName);

internal IEnumerable<Class> GetAllClasses()
{
yield return this;
foreach (Class implementation in DistinctAdditionalImplementations())
{
yield return implementation;
}
}
}
2 changes: 1 addition & 1 deletion Source/Mockolate.SourceGenerators/MockGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ private static void Execute(ImmutableArray<MockClass> mocksToGenerate, SourcePro
{
context.AddSource($"MockFor{mockToGenerate.Name}Extensions.g.cs",
SourceText.From(
Sources.Sources.ForMockCombinationExtensions(mockToGenerate.Name, mockToGenerate.MockClass),
Sources.Sources.ForMockCombinationExtensions(mockToGenerate.Name, mockToGenerate.MockClass, mockToGenerate.MockClass.DistinctAdditionalImplementations()),
Encoding.UTF8));
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
using System.Text;
using Microsoft.CodeAnalysis;
using Mockolate.SourceGenerators.Entities;
using Type = Mockolate.SourceGenerators.Entities.Type;

namespace Mockolate.SourceGenerators.Sources;

#pragma warning disable S3776 // Cognitive Complexity of methods should not be too high
internal static partial class Sources
{
public static string ForMockCombinationExtensions(string name, MockClass mockClass)
public static string ForMockCombinationExtensions(string name, MockClass mockClass,
IEnumerable<Class> distinctAdditionalImplementations)
{
StringBuilder sb = InitializeBuilder([
"Mockolate.Exceptions",
Expand Down Expand Up @@ -56,282 +55,69 @@ private static Mock<T> GetMockOrThrow<T>(T subject)
""");
sb.AppendLine();

if (mockClass.Delegate is not null)
sb.Append("\textension(").Append(mockClass.ClassFullName).AppendLine(" subject)");
sb.AppendLine("\t{");

HashSet<string> usedNames = [];
foreach (Class @class in distinctAdditionalImplementations)
{
AppendDelegateExtensions(sb, mockClass, mockClass.Delegate);
AppendAdditionalMockExtensions(sb, @class, usedNames);
}
else
{
if (mockClass.AdditionalImplementations.Any())
{
AppendMockExtensions(sb, mockClass);
}

sb.AppendLine();
}
sb.AppendLine("\t}");

sb.AppendLine("}");
sb.AppendLine("#nullable disable");
return sb.ToString();
}

private static void AppendDelegateExtensions(StringBuilder sb, MockClass mockClass, Method method)
private static void AppendAdditionalMockExtensions(StringBuilder sb, Class @class, HashSet<string> usedNames)
{
#region Setup

sb.Append("\textension(IMockSetup<").Append(mockClass.ClassFullName).AppendLine("> setup)");
sb.AppendLine("\t{");
sb.Append("\t\t/// <summary>").AppendLine();
sb.Append("\t\t/// Sets up the delegate <see cref=\"").Append(mockClass.ClassFullName.EscapeForXmlDoc())
.Append("\" /> on the mock.").AppendLine();
sb.Append("\t\t/// </summary>").AppendLine();
if (method.ReturnType != Type.Void)
{
sb.Append("\t\tpublic ReturnMethodSetup<")
.Append(method.ReturnType.Fullname);
foreach (MethodParameter parameter in method.Parameters)
{
sb.Append(", ").Append(parameter.Type.Fullname);
}

sb.Append("> Delegate(");
}
else
{
sb.Append("\t\tpublic VoidMethodSetup");
if (method.Parameters.Count > 0)
{
sb.Append('<');
int index = 0;
foreach (MethodParameter parameter in method.Parameters)
{
if (index++ > 0)
{
sb.Append(", ");
}

sb.Append(parameter.Type.Fullname);
}

sb.Append('>');
}

sb.Append(" Delegate(");
}

int i = 0;
foreach (MethodParameter parameter in method.Parameters)
{
if (i++ > 0)
{
sb.Append(", ");
}

sb.Append(parameter.RefKind switch
{
RefKind.Ref => "IRefParameter<",
RefKind.Out => "IOutParameter<",
_ => "IParameter<",
}).Append(parameter.Type.Fullname)
.Append('>');
if (parameter.RefKind is not RefKind.Ref and not RefKind.Out)
{
sb.Append('?');
}

sb.Append(' ').Append(parameter.Name);
}

sb.Append(")");
if (method.GenericParameters is not null && method.GenericParameters.Value.Count > 0)
{
foreach (GenericParameter gp in method.GenericParameters.Value)
{
gp.AppendWhereConstraint(sb, "\t\t\t");
}
}

sb.AppendLine();

sb.AppendLine("\t\t{");

if (method.ReturnType != Type.Void)
int nameSuffix = 1;
string name = @class.ClassName.Replace('.', '_');
while (!usedNames.Add(name))
{
sb.Append("\t\t\tvar methodSetup = new ReturnMethodSetup<")
.Append(method.ReturnType.Fullname);
foreach (MethodParameter parameter in method.Parameters)
{
sb.Append(", ").Append(parameter.Type.Fullname);
}

sb.Append(">");
}
else
{
sb.Append("\t\t\tvar methodSetup = new VoidMethodSetup");

if (method.Parameters.Count > 0)
{
sb.Append('<');
int index = 0;
foreach (MethodParameter parameter in method.Parameters)
{
if (index++ > 0)
{
sb.Append(", ");
}

sb.Append(parameter.Type.Fullname);
}

sb.Append('>');
}
}

sb.Append("(\"").Append(mockClass.ClassFullName).Append('.').Append(method.Name).Append("\"");
foreach (MethodParameter parameter in method.Parameters)
{
sb.Append(", new NamedParameter(\"").Append(parameter.Name).Append("\", ").Append(parameter.Name);
if (parameter.RefKind is not RefKind.Ref and not RefKind.Out)
{
sb.Append(" ?? It.IsNull<").Append(parameter.Type.Fullname)
.Append(">()");
}

sb.Append(")");
name = $"{@class.ClassName.Replace('.', '_')}__{++nameSuffix}";
}

sb.Append(");").AppendLine();
sb.AppendLine("\t\t\tif (setup is IMockSetup mockSetup)");
sb.AppendLine("\t\t\t{");
sb.AppendLine("\t\t\t\tmockSetup.RegisterMethod(methodSetup);");
sb.AppendLine("\t\t\t}");
sb.AppendLine("\t\t\treturn methodSetup;");
sb.AppendLine("\t\t}");
sb.AppendLine("\t}");

#endregion

sb.AppendLine();

#region Verify

sb.Append("\textension(IMockVerify<").Append(mockClass.ClassFullName).Append(", Mock<")
.Append(mockClass.ClassFullName).Append(">>").Append(" verify)").AppendLine();
sb.AppendLine("\t{");
sb.Append("\t\t/// <summary>").AppendLine();
sb.Append("\t\t/// Verifies the delegate invocations for <see cref=\"")
.Append(mockClass.ClassFullName.EscapeForXmlDoc()).Append("\"/> on the mock.").AppendLine();
sb.Append("\t\t/// Sets up the mock for <see cref=\"").Append(@class.ClassFullName.EscapeForXmlDoc())
.Append("\" />")
.AppendLine();
sb.Append("\t\t/// </summary>").AppendLine();
sb.Append("\t\tpublic VerificationResult<").Append(mockClass.ClassFullName).Append("> Invoked(");
i = 0;
foreach (MethodParameter parameter in method.Parameters)
sb.Append("\t\tpublic IMockSetup<").Append(@class.ClassFullName).Append("> Setup").Append(name)
.Append("Mock")
.AppendLine();
sb.Append("\t\t\t=> new Mock<").Append(@class.ClassFullName).Append(">((").Append(@class.ClassFullName)
.Append(")subject, GetMockOrThrow(subject).Registrations);")
.AppendLine();
if (@class.AllEvents().Any())
{
if (i++ > 0)
{
sb.Append(", ");
}

sb.Append(parameter.RefKind switch
{
RefKind.Ref => "IVerifyRefParameter<",
RefKind.Out => "IVerifyOutParameter<",
_ => "IParameter<",
}).Append(parameter.Type.Fullname)
.Append('>');
if (parameter.RefKind is not RefKind.Ref and not RefKind.Out)
{
sb.Append('?');
}

sb.Append(' ').Append(parameter.Name);
}

sb.Append(")").AppendLine();
sb.Append("\t\t{").AppendLine();
sb.Append("\t\t\tIMockInvoked<IMockVerify<").Append(mockClass.ClassFullName).Append(", Mock<")
.Append(mockClass.ClassFullName).Append(">>> invoked = (IMockInvoked<IMockVerify<")
.Append(mockClass.ClassFullName).Append(", Mock<")
.Append(mockClass.ClassFullName).Append(">>>)verify;").AppendLine();
sb.Append("\t\t\treturn invoked.Method(\"")
.Append(mockClass.ClassFullName).Append('.').Append(method.Name)
.Append("\"");

foreach (MethodParameter parameter in method.Parameters)
{
sb.Append(", ");
sb.Append(parameter.Name);
if (parameter.RefKind is not RefKind.Ref and not RefKind.Out)
{
sb.Append(" ?? It.IsNull<").Append(parameter.Type.Fullname)
.Append(">()");
}
}

sb.AppendLine(");");
sb.Append("\t\t}").AppendLine();

sb.AppendLine("\t}");

#endregion
}

private static void AppendMockExtensions(StringBuilder sb, MockClass mockClass)
{
sb.Append("\textension(").Append(mockClass.ClassFullName).AppendLine(" subject)");
sb.AppendLine("\t{");

HashSet<string> usedNames = [];
foreach (Class? @class in mockClass.DistinctAdditionalImplementations())
{
sb.AppendLine();
int nameSuffix = 1;
string name = @class.ClassName.Replace('.', '_');
while (!usedNames.Add(name))
{
name = $"{@class.ClassName.Replace('.', '_')}__{++nameSuffix}";
}

sb.Append("\t\t/// <summary>").AppendLine();
sb.Append("\t\t/// Sets up the mock for <see cref=\"").Append(@class.ClassFullName.EscapeForXmlDoc())
.Append("\" />")
.AppendLine();
sb.Append("\t\t/// </summary>").AppendLine();
sb.Append("\t\tpublic IMockSetup<").Append(@class.ClassFullName).Append("> Setup").Append(name)
.Append("Mock")
.AppendLine();
sb.Append("\t\t\t=> new Mock<").Append(@class.ClassFullName).Append(">((").Append(@class.ClassFullName)
.Append(")subject, GetMockOrThrow(subject).Registrations);")
.AppendLine();
if (@class.AllEvents().Any())
{
sb.AppendLine();
sb.Append("\t\t/// <summary>").AppendLine();
sb.Append("\t\t/// Raise events on the mock for <see cref=\"")
.Append(@class.ClassFullName.EscapeForXmlDoc())
.Append("\" />").AppendLine();
sb.Append("\t\t/// </summary>").AppendLine();
sb.Append("\t\tpublic IMockRaises<").Append(@class.ClassFullName).Append("> RaiseOn")
.Append(name).Append("Mock").AppendLine();
sb.Append("\t\t\t=> new Mock<").Append(@class.ClassFullName).Append(">((").Append(@class.ClassFullName)
.Append(")subject, GetMockOrThrow(subject).Registrations);")
.AppendLine();
}

sb.AppendLine();
sb.Append("\t\t/// <summary>").AppendLine();
sb.Append("\t\t/// Verifies the interactions with the mocked subject of <see cref=\"")
.Append(@class.ClassFullName.EscapeForXmlDoc()).Append("\" /> on the mock.").AppendLine();
sb.Append("\t\t/// Raise events on the mock for <see cref=\"")
.Append(@class.ClassFullName.EscapeForXmlDoc())
.Append("\" />").AppendLine();
sb.Append("\t\t/// </summary>").AppendLine();
sb.Append("\t\tpublic IMockVerify<").Append(@class.ClassFullName).Append("> VerifyOn").Append(name)
.Append("Mock")
.AppendLine();
sb.Append("\t\tpublic IMockRaises<").Append(@class.ClassFullName).Append("> RaiseOn")
.Append(name).Append("Mock").AppendLine();
sb.Append("\t\t\t=> new Mock<").Append(@class.ClassFullName).Append(">((").Append(@class.ClassFullName)
.Append(")subject, GetMockOrThrow(subject).Registrations);")
.AppendLine();
}

sb.AppendLine("\t}");
sb.AppendLine();
sb.Append("\t\t/// <summary>").AppendLine();
sb.Append("\t\t/// Verifies the interactions with the mocked subject of <see cref=\"")
.Append(@class.ClassFullName.EscapeForXmlDoc()).Append("\" /> on the mock.").AppendLine();
sb.Append("\t\t/// </summary>").AppendLine();
sb.Append("\t\tpublic IMockVerify<").Append(@class.ClassFullName).Append("> VerifyOn").Append(name)
.Append("Mock")
.AppendLine();
sb.Append("\t\t\t=> new Mock<").Append(@class.ClassFullName).Append(">((").Append(@class.ClassFullName)
.Append(")subject, GetMockOrThrow(subject).Registrations);")
.AppendLine();
}
}
#pragma warning restore S3776 // Cognitive Complexity of methods should not be too high
8 changes: 1 addition & 7 deletions Source/Mockolate.SourceGenerators/Sources/Sources.ForMock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,7 @@ namespace Mockolate.Generated;
sb.Append("/// <summary>").AppendLine();
sb.Append("/// A mock implementing <see cref=\"")
.Append(mockClass.ClassFullName.EscapeForXmlDoc())
.Append("\" />");
foreach (Class? additional in mockClass.DistinctAdditionalImplementations())
{
sb.Append(" and <see cref=\"").Append(additional.ClassFullName.EscapeForXmlDoc()).Append("\" />");
}

sb.AppendLine(".");
.Append("\" />.");
sb.Append("/// </summary>").AppendLine();
sb.Append("internal class MockFor").Append(name).Append(" : IMockSubject<").Append(mockClass.ClassFullName)
.Append(">").AppendLine();
Expand Down
Loading
Loading