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
187 changes: 180 additions & 7 deletions Source/Mockolate.SourceGenerators/Sources/Sources.ForMock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@ namespace Mockolate.Generated;
.Append(mockClass.ClassFullName).Append(">.Mock => _mock;").AppendLine();
sb.Append("\t[DebuggerBrowsable(DebuggerBrowsableState.Never)]").AppendLine();
sb.Append("\tprivate readonly Mock<").Append(mockClass.ClassFullName).Append("> _mock;").AppendLine();
if (mockClass.IsInterface)
{
sb.Append("\tprivate readonly ").Append(mockClass.ClassFullName).Append("? _wrapped;").AppendLine();
}
sb.AppendLine();
if (mockClass.Constructors?.Count > 0)
{
Expand Down Expand Up @@ -182,11 +186,12 @@ namespace Mockolate.Generated;
if (mockClass.IsInterface)
{
sb.Append("\t/// <inheritdoc cref=\"MockFor").Append(name).Append("\" />").AppendLine();
sb.Append("\tpublic MockFor").Append(name).Append("(MockBehavior mockBehavior)").AppendLine();
sb.Append("\tpublic MockFor").Append(name).Append("(MockBehavior mockBehavior, ").Append(mockClass.ClassFullName).Append("? wrapped = null)").AppendLine();
sb.Append("\t{").AppendLine();
sb.Append("\t\t_mock = new Mock<").Append(mockClass.ClassFullName)
.Append(">(this, new MockRegistration(mockBehavior, \"").Append(mockClass.DisplayString)
.Append("\"));").AppendLine();
sb.Append("\t\tthis._wrapped = wrapped;").AppendLine();
sb.Append("\t}").AppendLine();
sb.AppendLine();
}
Expand Down Expand Up @@ -335,10 +340,34 @@ private static void AppendMockSubject_ImplementClass_AddEvent(StringBuilder sb,
}

sb.AppendLine("\t{");
sb.Append("\t\tadd => MockRegistrations.AddEvent(").Append(@event.GetUniqueNameString())
.Append(", value?.Target, value?.Method);").AppendLine();
sb.Append("\t\tremove => MockRegistrations.RemoveEvent(")
.Append(@event.GetUniqueNameString()).Append(", value?.Target, value?.Method);").AppendLine();
if (isClassInterface && !explicitInterfaceImplementation && @event.ExplicitImplementation is null)
{
sb.Append("\t\tadd").AppendLine();
sb.Append("\t\t{").AppendLine();
sb.Append("\t\t\tMockRegistrations.AddEvent(").Append(@event.GetUniqueNameString())
.Append(", value?.Target, value?.Method);").AppendLine();
sb.Append("\t\t\tif (this._wrapped is not null)").AppendLine();
sb.Append("\t\t\t{").AppendLine();
sb.Append("\t\t\t\tthis._wrapped.").Append(@event.Name).Append(" += value;").AppendLine();
sb.Append("\t\t\t}").AppendLine();
sb.Append("\t\t}").AppendLine();
sb.Append("\t\tremove").AppendLine();
sb.Append("\t\t{").AppendLine();
sb.Append("\t\t\tMockRegistrations.RemoveEvent(").Append(@event.GetUniqueNameString())
.Append(", value?.Target, value?.Method);").AppendLine();
sb.Append("\t\t\tif (this._wrapped is not null)").AppendLine();
sb.Append("\t\t\t{").AppendLine();
sb.Append("\t\t\t\tthis._wrapped.").Append(@event.Name).Append(" -= value;").AppendLine();
sb.Append("\t\t\t}").AppendLine();
sb.Append("\t\t}").AppendLine();
}
else
{
sb.Append("\t\tadd => MockRegistrations.AddEvent(").Append(@event.GetUniqueNameString())
.Append(", value?.Target, value?.Method);").AppendLine();
sb.Append("\t\tremove => MockRegistrations.RemoveEvent(")
.Append(@event.GetUniqueNameString()).Append(", value?.Target, value?.Method);").AppendLine();
}
sb.AppendLine("\t}");
}

Expand Down Expand Up @@ -396,7 +425,48 @@ property.IndexerParameters is not null

sb.AppendLine("get");
sb.AppendLine("\t\t{");
if (!isClassInterface && !property.IsAbstract)
if (isClassInterface && !explicitInterfaceImplementation && property.ExplicitImplementation is null)
{
if (property is { IsIndexer: true, IndexerParameters: not null, })
{
string indexerResultVarName =
Helpers.GetUniqueLocalVariableName("indexerResult", property.IndexerParameters.Value);
string baseResultVarName =
Helpers.GetUniqueLocalVariableName("baseResult", property.IndexerParameters.Value);

sb.Append("\t\t\tif (this._wrapped is null)").AppendLine();
sb.Append("\t\t\t{").AppendLine();
sb.Append("\t\t\t\treturn MockRegistrations.GetIndexer<")
.AppendTypeOrWrapper(property.Type).Append(">(")
.Append(string.Join(", ", property.IndexerParameters.Value.Select(p => p.ToNameOrWrapper())))
.Append(").GetResult(() => ")
.AppendDefaultValueGeneratorFor(property.Type, "MockRegistrations.Behavior.DefaultValue")
.Append(");").AppendLine();
sb.Append("\t\t\t}").AppendLine();
sb.Append("\t\t\tvar ").Append(indexerResultVarName).Append(" = MockRegistrations.GetIndexer<")
.AppendTypeOrWrapper(property.Type).Append(">(")
.Append(string.Join(", ", property.IndexerParameters.Value.Select(p => p.ToNameOrWrapper())))
.AppendLine(");");
sb.Append("\t\t\tvar ").Append(baseResultVarName).Append(" = this._wrapped[")
.Append(string.Join(", ", property.IndexerParameters.Value.Select(p => p.Name)))
.Append("];").AppendLine();
sb.Append("\t\t\treturn ").Append(indexerResultVarName).Append(".GetResult(")
.Append(baseResultVarName)
.Append(", () => ")
.AppendDefaultValueGeneratorFor(property.Type, "MockRegistrations.Behavior.DefaultValue")
.Append(");").AppendLine();
}
else
{
sb.Append(
"\t\t\treturn MockRegistrations.GetProperty<")
.AppendTypeOrWrapper(property.Type).Append(">(")
.Append(property.GetUniqueNameString()).Append(", () => ")
.AppendDefaultValueGeneratorFor(property.Type, "MockRegistrations.Behavior.DefaultValue")
.Append(", this._wrapped is null ? null : () => this._wrapped.").Append(property.Name).Append(");").AppendLine();
}
}
else if (!isClassInterface && !property.IsAbstract)
{
if (property is { IsIndexer: true, IndexerParameters: not null, })
{
Expand Down Expand Up @@ -468,7 +538,36 @@ property.IndexerParameters is not null
sb.AppendLine("set");
sb.AppendLine("\t\t{");

if (property is { IsIndexer: true, IndexerParameters: not null, })
if (isClassInterface && !explicitInterfaceImplementation && property.ExplicitImplementation is null)
{
if (property is { IsIndexer: true, IndexerParameters: not null, })
{
sb.Append(
"\t\t\tMockRegistrations.SetIndexer<")
.Append(property.Type.Fullname)
.Append(">(value, ")
.Append(string.Join(", ", property.IndexerParameters.Value.Select(p => p.ToNameOrWrapper())))
.Append(");").AppendLine();

sb.Append("\t\t\tif (this._wrapped is not null)").AppendLine();
sb.Append("\t\t\t{").AppendLine();
sb.Append("\t\t\t\tthis._wrapped[")
.Append(string.Join(", ", property.IndexerParameters.Value.Select(p => p.Name)))
.AppendLine("] = value;");
sb.Append("\t\t\t}").AppendLine();
}
else
{
sb.Append(
"\t\t\tMockRegistrations.SetProperty(").Append(property.GetUniqueNameString())
.Append(", value);").AppendLine();
sb.Append("\t\t\tif (this._wrapped is not null)").AppendLine();
sb.Append("\t\t\t{").AppendLine();
sb.Append("\t\t\t\tthis._wrapped.").Append(property.Name).Append(" = value;").AppendLine();
sb.Append("\t\t\t}").AppendLine();
}
}
else if (property is { IsIndexer: true, IndexerParameters: not null, })
{
if (!isClassInterface && !property.IsAbstract)
{
Expand Down Expand Up @@ -609,6 +708,80 @@ private static void AppendMockSubject_ImplementClass_AddMethod(StringBuilder sb,

if (isClassInterface || method.IsAbstract)
{
if (!explicitInterfaceImplementation && isClassInterface)
{
string baseResultVarName = Helpers.GetUniqueLocalVariableName("baseResult", method.Parameters);
if (method.ReturnType != Type.Void)
{
sb.Append(
"\t\tif (this._wrapped is not null)")
.AppendLine();
sb.Append("\t\t{").AppendLine();
sb.Append("\t\t\tvar ").Append(baseResultVarName).Append(" = this._wrapped").Append(".")
.Append(method.Name).Append('(')
.Append(string.Join(", ", method.Parameters.Select(p => $"{p.RefKind.GetString()}{p.Name}")))
.Append(");").AppendLine();
}
else
{
sb.Append(
"\t\tif (this._wrapped is not null)")
.AppendLine();
sb.Append("\t\t{").AppendLine();
sb.Append("\t\t\tthis._wrapped").Append(".")
.Append(method.Name).Append('(')
.Append(string.Join(", ", method.Parameters.Select(p => $"{p.RefKind.GetString()}{p.Name}")))
.Append(");").AppendLine();
}

foreach (MethodParameter parameter in method.Parameters)
{
if (parameter.RefKind == RefKind.Out)
{
sb.Append(
"\t\t\tif (").Append(methodExecutionVarName).Append(".HasSetupResult == true)")
.AppendLine();
sb.Append("\t\t\t{").AppendLine();
sb.Append("\t\t\t\t").Append(parameter.Name).Append(" = ").Append(methodExecutionVarName)
.Append(".SetOutParameter<")
.Append(parameter.Type.Fullname).Append(">(\"").Append(parameter.Name)
.Append("\", () => ")
.AppendDefaultValueGeneratorFor(parameter.Type,
"MockRegistrations.Behavior.DefaultValue")
.Append(");").AppendLine();
sb.Append("\t\t\t}").AppendLine().AppendLine();
}
else if (parameter.RefKind == RefKind.Ref)
{
sb.Append(
"\t\t\tif (").Append(methodExecutionVarName).Append(".HasSetupResult == true)")
.AppendLine();
sb.Append("\t\t\t{").AppendLine();
sb.Append("\t\t\t\t").Append(parameter.Name).Append(" = ").Append(methodExecutionVarName)
.Append(".SetRefParameter<")
.Append(parameter.Type.Fullname).Append(">(\"").Append(parameter.Name).Append("\", ")
.Append(parameter.Name).Append(");").AppendLine();
sb.Append("\t\t\t}").AppendLine().AppendLine();
}
}

if (method.ReturnType != Type.Void)
{
sb.Append(
"\t\t\tif (!").Append(methodExecutionVarName).Append(".HasSetupResult)")
.AppendLine();
sb.Append("\t\t\t{").AppendLine();
sb.Append("\t\t\t\t").Append(methodExecutionVarName).Append(".TriggerCallbacks(")
.Append(
string.Join(", ", method.Parameters.Select(p => p.ToNameOrNull())))
.Append(");").AppendLine();
sb.Append("\t\t\t\treturn ").Append(baseResultVarName).Append(";").AppendLine();
sb.Append("\t\t\t}").AppendLine();
}

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

foreach (MethodParameter parameter in method.Parameters)
{
if (parameter.RefKind == RefKind.Out)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,49 @@ namespace Mockolate;
}

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

sb.AppendLine();
sb.AppendLine(
"\t\tpartial void GenerateWrapped<T>(T instance, MockBehavior mockBehavior, Action<IMockSetup<T>>[] setups)");
sb.Append("\t\t{").AppendLine();
index = 0;
foreach ((string Name, MockClass MockClass) mock in mocks.Where(m => m.MockClass.AdditionalImplementations.Count == 0))
{
if (index++ > 0)
{
sb.Append("\t\t\telse ");
}
else
{
sb.Append("\t\t\t");
}

sb.Append("if (typeof(T) == typeof(").Append(mock.MockClass.ClassFullName).Append("))").AppendLine();
sb.Append("\t\t\t{").AppendLine();

sb.Append("\t\t\t\tMockRegistration mockRegistration = new MockRegistration(mockBehavior, \"")
.Append(mock.MockClass.DisplayString).Append("\");").AppendLine();

if (mock.MockClass.IsInterface)
{
sb.Append("\t\t\t\t_value = new MockFor").Append(mock.Name).Append("(mockBehavior, instance as ").Append(mock.MockClass.ClassFullName).Append(");").AppendLine();
sb.Append("\t\t\t\tif (setups.Length > 0)").AppendLine();
sb.Append("\t\t\t\t{").AppendLine();
sb.Append("\t\t\t\t\tIMockSetup<").Append(mock.MockClass.ClassFullName)
.Append("> setupTarget = ((IMockSubject<").Append(mock.MockClass.ClassFullName)
.Append(">)_value).Mock;").AppendLine();
sb.Append("\t\t\t\t\tforeach (Action<IMockSetup<").Append(mock.MockClass.ClassFullName)
.Append(">> setup in setups)").AppendLine();
sb.Append("\t\t\t\t\t{").AppendLine();
sb.Append("\t\t\t\t\t\tsetup.Invoke(setupTarget);").AppendLine();
sb.Append("\t\t\t\t\t}").AppendLine();
sb.Append("\t\t\t\t}").AppendLine();
}

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

sb.AppendLine("\t}");
sb.AppendLine();
}
Expand Down
70 changes: 70 additions & 0 deletions Source/Mockolate.SourceGenerators/Sources/Sources.cs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,65 @@ public T Create<T>(BaseClass.ConstructorParameters constructorParameters, params
sb.AppendLine("\t}");
sb.AppendLine();

sb.AppendLine("""
/// <summary>
/// Wraps a concrete instance with a mock proxy that intercepts and delegates method calls,
/// supporting setup and verification on the wrapped instance.
/// </summary>
/// <typeparam name="T">Type to wrap, which can be an interface or a class.</typeparam>
/// <param name="instance">The concrete instance to wrap.</param>
/// <param name="setups">Optional setup actions to configure the mock.</param>
/// <remarks>
/// When no setup is specified for a method, the call is delegated to the wrapped instance.
/// Setup and verification work the same as with regular mocks.
/// </remarks>
[MockGenerator]
public static T Wrap<T>(T instance, params Action<IMockSetup<T>>[] setups)
where T : class
{
if (instance == null)
{
throw new ArgumentNullException(nameof(instance));
}

ThrowIfNotMockable(typeof(T));

return new MockGenerator().GetWrapped<T>(instance, MockBehavior.Default, setups)
?? throw new MockException("Could not generate wrapped Mock<T>. Did the source generator run correctly?");
}
""");
sb.AppendLine();

sb.AppendLine("""
/// <summary>
/// Wraps a concrete instance with a mock proxy that intercepts and delegates method calls,
/// supporting setup and verification on the wrapped instance.
/// </summary>
/// <typeparam name="T">Type to wrap, which can be an interface or a class.</typeparam>
/// <param name="instance">The concrete instance to wrap.</param>
/// <param name="mockBehavior">The behavior settings for the mock.</param>
/// <param name="setups">Optional setup actions to configure the mock.</param>
/// <remarks>
/// When no setup is specified for a method, the call is delegated to the wrapped instance.
/// Setup and verification work the same as with regular mocks.
/// </remarks>
[MockGenerator]
public static T Wrap<T>(T instance, MockBehavior mockBehavior, params Action<IMockSetup<T>>[] setups)
where T : class
{
if (instance == null)
{
throw new ArgumentNullException(nameof(instance));
}

ThrowIfNotMockable(typeof(T));

return new MockGenerator().GetWrapped<T>(instance, mockBehavior, setups)
?? throw new MockException("Could not generate wrapped Mock<T>. Did the source generator run correctly?");
}
""");
sb.AppendLine();

sb.AppendLine("""
private static void ThrowIfNotMockable(Type type)
{
Expand All @@ -414,6 +473,7 @@ private partial class MockGenerator
#pragma warning restore CS0649

partial void Generate<T>(BaseClass.ConstructorParameters? constructorParameters, MockBehavior mockBehavior, Action<IMockSetup<T>>[] setups, params Type[] types);
partial void GenerateWrapped<T>(T instance, MockBehavior mockBehavior, Action<IMockSetup<T>>[] setups);

public object? Get(BaseClass.ConstructorParameters? constructorParameters, MockBehavior mockBehavior, Type type)
{
Expand Down Expand Up @@ -442,6 +502,16 @@ private partial class MockGenerator
""");
}

sb.AppendLine();
sb.AppendLine("""
public T? GetWrapped<T>(T instance, MockBehavior mockBehavior, Action<IMockSetup<T>>[] setups)
where T : class
{
GenerateWrapped<T>(instance, mockBehavior, setups);
return _value as T;
}
""");

sb.AppendLine("""
}
}
Expand Down
Loading
Loading