From 0e1055a0383f60bcf8ccf054470c08ca8cca81e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Breu=C3=9F=20Valentin?= Date: Mon, 22 Dec 2025 14:15:48 +0100 Subject: [PATCH 1/2] refactor: remove dead code in delegate source generation --- .../Sources/Sources.ForMock.Extensions.cs | 27 +------- .../Sources/Sources.ForMock.cs | 2 +- .../GeneralTests.cs | 55 ++++++++++++++- .../MockGeneratorTests.cs | 69 ++++++++++++++++--- .../Sources/ForMockTests.DelegateTests.cs | 10 +-- .../ForMockTests.ImplementClassTests.cs | 41 +++++++++-- .../TestHelpers/Generator.cs | 1 + .../Mockolate.Tests/MockTests.CreateTests.cs | 13 ++++ 8 files changed, 168 insertions(+), 50 deletions(-) diff --git a/Source/Mockolate.SourceGenerators/Sources/Sources.ForMock.Extensions.cs b/Source/Mockolate.SourceGenerators/Sources/Sources.ForMock.Extensions.cs index 00c62891..d856a456 100644 --- a/Source/Mockolate.SourceGenerators/Sources/Sources.ForMock.Extensions.cs +++ b/Source/Mockolate.SourceGenerators/Sources/Sources.ForMock.Extensions.cs @@ -100,16 +100,7 @@ namespace Mockolate; sb.Append(' ').Append(parameter.Name); } - sb.Append(")"); - if (@delegate.GenericParameters is not null && @delegate.GenericParameters.Value.Count > 0) - { - foreach (GenericParameter gp in @delegate.GenericParameters.Value) - { - gp.AppendWhereConstraint(sb, "\t\t\t"); - } - } - - sb.AppendLine(); + sb.Append(")").AppendLine(); sb.Append("\t\t\t=> CastToMockOrThrow(verify).Method(").Append(@delegate.GetUniqueNameString()); @@ -134,16 +125,7 @@ namespace Mockolate; .AppendLine(); sb.Append("\t\t/// ").AppendLine(); sb.Append("\t\tpublic VerificationResult<").Append(@class.ClassFullName).Append("> Invoked") - .Append("(IParameters parameters)"); - if (@delegate.GenericParameters is not null && @delegate.GenericParameters.Value.Count > 0) - { - foreach (GenericParameter gp in @delegate.GenericParameters.Value) - { - gp.AppendWhereConstraint(sb, "\t\t\t"); - } - } - - sb.AppendLine(); + .Append("(IParameters parameters)").AppendLine(); sb.Append("\t\t\t=> CastToMockOrThrow(verify).Method(").Append(@delegate.GetUniqueNameString()); sb.AppendLine(", parameters);"); @@ -607,10 +589,7 @@ property is .Append(property.Name.EscapeForXmlDoc()).Append("\"/>.").AppendLine(); sb.Append("\t\t/// ").AppendLine(); sb.Append("\t\tpublic IPropertySetup<").Append(property.Type.Fullname).Append("> ") - .Append(property.IndexerParameters is not null - ? property.Name.Replace("[]", - $"[{string.Join(", ", property.IndexerParameters.Value.Select(p => $"IParameter<{p.Type.Fullname}> {p.Name}"))}]") - : property.Name).AppendLine(); + .Append(property.Name).AppendLine(); sb.AppendLine("\t\t{"); sb.AppendLine("\t\t\tget"); diff --git a/Source/Mockolate.SourceGenerators/Sources/Sources.ForMock.cs b/Source/Mockolate.SourceGenerators/Sources/Sources.ForMock.cs index 8ae15ed2..80117bb3 100644 --- a/Source/Mockolate.SourceGenerators/Sources/Sources.ForMock.cs +++ b/Source/Mockolate.SourceGenerators/Sources/Sources.ForMock.cs @@ -175,7 +175,7 @@ namespace Mockolate.Generated; sb.Append("\t}").AppendLine(); sb.AppendLine(); } - else if (mockClass.Constructors?.Count > 0) + else if (mockClass.Constructors is not null) { foreach (Method constructor in mockClass.Constructors) { diff --git a/Tests/Mockolate.SourceGenerators.Tests/GeneralTests.cs b/Tests/Mockolate.SourceGenerators.Tests/GeneralTests.cs index 6dd49e37..7cbbcf19 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/GeneralTests.cs +++ b/Tests/Mockolate.SourceGenerators.Tests/GeneralTests.cs @@ -177,6 +177,54 @@ await That(result.Sources).ContainsKey("MockForIListMyRecord.g.cs").WhoseValue .Contains("internal class MockForIListMyRecord : System.Collections.Generic.IList"); } + [Fact] + public async Task MultipleInterfacesWithSameName_ShouldAddSuffixToSetupAndVerifyMethods() + { + GeneratorResult result = Generator + .Run(""" + using System.Collections.Generic; + + namespace MyCode + { + public class Program + { + public static void Main(string[] args) + { + var x = Mockolate.Mock.Create(); + } + } + + public interface IMyInterface + { + new void MyMethod(int v1); + } + } + + namespace MyCode.N1 + { + public interface IMyInterface + { + new void MyMethod(int v1); + } + } + + namespace MyCode.N2 + { + public interface IMyInterface + { + new void MyMethod(int v1); + } + } + + """, typeof(IList<>)); + + await That(result.Diagnostics).IsEmpty(); + + await That(result.Sources).ContainsKey("MockForIMyInterface_IMyInterface_IMyInterfaceExtensions.g.cs") + .WhoseValue + .Contains("public IMockSetup SetupIMyInterface__2Mock"); + } + [Fact] public async Task ObsoleteAttributes_ShouldBeRepeatedInMock() { @@ -394,13 +442,13 @@ public interface IMyService )] event EventHandler MyEvent; } - + public enum MyEnum { Value1 = 1, Value2 = 2 } - + [Flags] public enum MyFlagEnum { @@ -449,7 +497,8 @@ public CustomAttribute( public MyFlagEnum EnumParam { get; set; } public string[] ArrayParam { get; set; } } - """, typeof(AllowNullAttribute), typeof(IDataParameter), typeof(LocalizableAttribute), typeof(AttributeUsageAttribute)); + """, typeof(AllowNullAttribute), typeof(IDataParameter), typeof(LocalizableAttribute), + typeof(AttributeUsageAttribute)); await That(result.Sources).ContainsKey("MockForIMyService.g.cs").WhoseValue .Contains(""" diff --git a/Tests/Mockolate.SourceGenerators.Tests/MockGeneratorTests.cs b/Tests/Mockolate.SourceGenerators.Tests/MockGeneratorTests.cs index 1a9e8545..5218991b 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/MockGeneratorTests.cs +++ b/Tests/Mockolate.SourceGenerators.Tests/MockGeneratorTests.cs @@ -160,7 +160,7 @@ await ThatAll( } [Fact] - public async Task WhenUsingMockCreateFromOtherNamespace_ShouldNotBeIncluded() + public async Task WhenUsingCustomMockGeneratorAttribute_ShouldNotBeIncluded() { GeneratorResult result = Generator .Run(""" @@ -182,10 +182,59 @@ public interface IMyInterface { } public class Mock { - public static Mock Create() => new Mock(); + [Mockolate.MockGenerator] + public static T Create() => default(T)!; } + } + """); - public class Mock{ } + await ThatAll( + That(result.Sources.Keys).IsEqualTo([ + "Mock.g.cs", + "MockBehaviorExtensions.g.cs", + "MockForIMyInterface.g.cs", + "MockForIMyInterfaceExtensions.g.cs", + "MockGeneratorAttribute.g.cs", + "MockRegistration.g.cs", + ]).InAnyOrder().IgnoringCase(), + That(result.Diagnostics).IsEmpty() + ); + } + + [Fact] + public async Task WhenUsingIncorrectMockGeneratorAttribute_ShouldNotBeIncluded() + { + GeneratorResult result = Generator + .Run(""" + using System; + using System.Threading; + using System.Threading.Tasks; + + namespace MyCode + { + public class Program + { + public static void Main(string[] args) + { + _ = Mock.Create(); + } + } + + public interface IMyInterface { } + + public class Mock + { + [Mockolate.Incorrect.MockGenerator] + public static T Create() => default(T)!; + } + } + + namespace Mockolate.Incorrect + { + [AttributeUsage(AttributeTargets.Method)] + internal class MockGeneratorAttribute : Attribute + { + } } """); @@ -293,13 +342,13 @@ public class MyOtherService { } """); await That(result.Sources.Keys).IsEqualTo([ - "Mock.g.cs", - "MockBehaviorExtensions.g.cs", - "MockGeneratorAttribute.g.cs", - "MockForIMyInterface1Extensions.g.cs", - "MockForIMyInterface2Extensions.g.cs", - "MockRegistration.g.cs", - ]).InAnyOrder(); + "Mock.g.cs", + "MockBehaviorExtensions.g.cs", + "MockGeneratorAttribute.g.cs", + "MockForIMyInterface1Extensions.g.cs", + "MockForIMyInterface2Extensions.g.cs", + "MockRegistration.g.cs", + ]).InAnyOrder(); } [Fact] diff --git a/Tests/Mockolate.SourceGenerators.Tests/Sources/ForMockTests.DelegateTests.cs b/Tests/Mockolate.SourceGenerators.Tests/Sources/ForMockTests.DelegateTests.cs index 8acf1556..b2d7b00d 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/Sources/ForMockTests.DelegateTests.cs +++ b/Tests/Mockolate.SourceGenerators.Tests/Sources/ForMockTests.DelegateTests.cs @@ -19,23 +19,23 @@ public static void Main(string[] args) _ = Mock.Create(); } - public delegate int DoSomething(int x, int y); + public delegate int DoSomething(int x, int y, out bool success); } """); await That(result.Sources) .ContainsKey("MockForProgramDoSomethingExtensions.g.cs").WhoseValue .Contains(""" - public IReturnMethodSetup Delegate(IParameter? x, IParameter? y) + public IReturnMethodSetup Delegate(IParameter? x, IParameter? y, IOutParameter success) { - var methodSetup = new ReturnMethodSetup("MyCode.Program.DoSomething.Invoke", new NamedParameter("x", (IParameter)(x ?? It.IsNull())), new NamedParameter("y", (IParameter)(y ?? It.IsNull()))); + var methodSetup = new ReturnMethodSetup("MyCode.Program.DoSomething.Invoke", new NamedParameter("x", (IParameter)(x ?? It.IsNull())), new NamedParameter("y", (IParameter)(y ?? It.IsNull())), new NamedParameter("success", (IParameter)(success))); CastToMockRegistrationOrThrow(setup).SetupMethod(methodSetup); return methodSetup; } """).IgnoringNewlineStyle().And .Contains(""" - public VerificationResult Invoked(IParameter? x, IParameter? y) - => CastToMockOrThrow(verify).Method("MyCode.Program.DoSomething.Invoke", new NamedParameter("x", (IParameter)(x ?? It.IsNull())), new NamedParameter("y", (IParameter)(y ?? It.IsNull()))); + public VerificationResult Invoked(IParameter? x, IParameter? y, IVerifyOutParameter success) + => CastToMockOrThrow(verify).Method("MyCode.Program.DoSomething.Invoke", new NamedParameter("x", (IParameter)(x ?? It.IsNull())), new NamedParameter("y", (IParameter)(y ?? It.IsNull())), new NamedParameter("success", (IParameter)(success))); """).IgnoringNewlineStyle(); } diff --git a/Tests/Mockolate.SourceGenerators.Tests/Sources/ForMockTests.ImplementClassTests.cs b/Tests/Mockolate.SourceGenerators.Tests/Sources/ForMockTests.ImplementClassTests.cs index ab1ab1a6..5aa19b1d 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/Sources/ForMockTests.ImplementClassTests.cs +++ b/Tests/Mockolate.SourceGenerators.Tests/Sources/ForMockTests.ImplementClassTests.cs @@ -295,15 +295,21 @@ public class Program public static void Main(string[] args) { _ = Mock.Create(); + _ = Mock.Create(); } } - + public class MyService { public virtual event EventHandler SomeEvent; public event EventHandler? SomeOtherEvent; protected virtual event EventHandler SomeProtectedEvent; } + + public class MyProtectedService + { + protected virtual event EventHandler SomeProtectedEvent; + } public interface IMyOtherService { @@ -351,7 +357,7 @@ public static void Main(string[] args) public interface IMyService { int this[int index] { get; set; } - int this[int index, bool isReadOnly] { get; } + int this[int index, bool? isReadOnly] { get; } int this[int index, string isWriteOnly] { set; } } """); @@ -382,8 +388,8 @@ public int this[int index] } """).IgnoringNewlineStyle().And .Contains(""" - /// - public int this[int index, bool isReadOnly] + /// + public int this[int index, bool? isReadOnly] { get { @@ -427,9 +433,10 @@ public class Program public static void Main(string[] args) { _ = Mock.Create(); + _ = Mock.Create(); } } - + public class MyService { public virtual int this[int index] { get; set; } @@ -437,6 +444,11 @@ public class MyService protected virtual int this[int index, string isWriteOnly] { set; } public int this[int index, long isNotVirtual] { get; set; } } + + public class MyProtectedService + { + protected virtual int this[int index, bool? isReadOnly] { get; set; } + } public interface IMyOtherService { @@ -931,9 +943,10 @@ public class Program public static void Main(string[] args) { _ = Mock.Create(); + _ = Mock.Create(); } } - + public class MyService { public virtual void MyMethod1(int index, ref int value1, out bool flag) @@ -946,6 +959,14 @@ protected virtual bool MyMethod2(int index, bool isReadOnly, ref int value1, out } public void MyNonVirtualMethod(); } + + public class MyProtectedService + { + protected virtual bool MyMethod(int index, bool isReadOnly, ref int value1, out bool flag) + { + flag = true; + } + } public interface IMyOtherService { @@ -1458,9 +1479,10 @@ public class Program public static void Main(string[] args) { _ = Mock.Create(); + _ = Mock.Create(); } } - + public class MyService { public virtual int SomeProperty1 { protected get; set; } @@ -1469,6 +1491,11 @@ public class MyService protected virtual bool? SomeWriteOnlyProperty { set; } public bool? SomeNonVirtualProperty { get; set; } } + + public class MyProtectedService + { + protected virtual bool? SomeProperty { get; set; } + } public interface IMyOtherService { diff --git a/Tests/Mockolate.SourceGenerators.Tests/TestHelpers/Generator.cs b/Tests/Mockolate.SourceGenerators.Tests/TestHelpers/Generator.cs index 07129ea1..1fd056b4 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/TestHelpers/Generator.cs +++ b/Tests/Mockolate.SourceGenerators.Tests/TestHelpers/Generator.cs @@ -16,6 +16,7 @@ public static class Generator "CS1061" /* 'type' does not contain a definition for 'name' and no accessible extension method 'name' accepting a first argument of type 'type' could be found (are you missing a using directive or an assembly reference?). */, // TODO: Remove the following errors when tests work with extension syntax "CS0106" /* The modifier 'public' is not valid for this item */, + "CS0109" /* The member 'member' does not hide an inherited member. The new keyword is not required */, "CS0116" /* A namespace cannot directly contain members such as fields or methods */, "CS1520" /* Method must have a return type */, "CS0710" /* Static classes cannot have instance constructors */, diff --git a/Tests/Mockolate.Tests/MockTests.CreateTests.cs b/Tests/Mockolate.Tests/MockTests.CreateTests.cs index 419950f8..07b34d5f 100644 --- a/Tests/Mockolate.Tests/MockTests.CreateTests.cs +++ b/Tests/Mockolate.Tests/MockTests.CreateTests.cs @@ -81,6 +81,19 @@ public async Task With3Arguments_OnlyFirstArgumentIsClass_ShouldForwardBehaviorT await That(((IHasMockRegistration)sut).Registrations.Behavior).IsSameAs(behavior); } + [Fact] + public async Task With3Arguments_SecondAndThirdAreClasses_ShouldThrow() + { + void Act() + { + _ = Mock.Create(); + } + + await That(Act).Throws() + .WithMessage( + "The mock declaration has 2 additional implementations that are not interfaces: Mockolate.Tests.TestHelpers.MyServiceBase, Mockolate.Tests.TestHelpers.MyServiceBase"); + } + [Fact] public async Task With3Arguments_SecondIsClass_ShouldThrow() { From d2400e1524792b327361754831f9b93ca0e17752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Breu=C3=9F=20Valentin?= Date: Mon, 22 Dec 2025 15:20:06 +0100 Subject: [PATCH 2/2] Fix review issues --- Tests/Mockolate.SourceGenerators.Tests/GeneralTests.cs | 6 +++--- .../TestHelpers/Generator.cs | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Tests/Mockolate.SourceGenerators.Tests/GeneralTests.cs b/Tests/Mockolate.SourceGenerators.Tests/GeneralTests.cs index 7cbbcf19..61b08089 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/GeneralTests.cs +++ b/Tests/Mockolate.SourceGenerators.Tests/GeneralTests.cs @@ -196,7 +196,7 @@ public static void Main(string[] args) public interface IMyInterface { - new void MyMethod(int v1); + void MyMethod(int v1); } } @@ -204,7 +204,7 @@ namespace MyCode.N1 { public interface IMyInterface { - new void MyMethod(int v1); + void MyMethod(int v1); } } @@ -212,7 +212,7 @@ namespace MyCode.N2 { public interface IMyInterface { - new void MyMethod(int v1); + void MyMethod(int v1); } } diff --git a/Tests/Mockolate.SourceGenerators.Tests/TestHelpers/Generator.cs b/Tests/Mockolate.SourceGenerators.Tests/TestHelpers/Generator.cs index 1fd056b4..07129ea1 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/TestHelpers/Generator.cs +++ b/Tests/Mockolate.SourceGenerators.Tests/TestHelpers/Generator.cs @@ -16,7 +16,6 @@ public static class Generator "CS1061" /* 'type' does not contain a definition for 'name' and no accessible extension method 'name' accepting a first argument of type 'type' could be found (are you missing a using directive or an assembly reference?). */, // TODO: Remove the following errors when tests work with extension syntax "CS0106" /* The modifier 'public' is not valid for this item */, - "CS0109" /* The member 'member' does not hide an inherited member. The new keyword is not required */, "CS0116" /* A namespace cannot directly contain members such as fields or methods */, "CS1520" /* Method must have a return type */, "CS0710" /* Static classes cannot have instance constructors */,