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
13 changes: 6 additions & 7 deletions Source/Mockolate.SourceGenerators/Entities/Class.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ public Class(ITypeSymbol type,
List<Event> events = ToListExcept(type.GetMembers().OfType<IEventSymbol>()
.Where(x => !x.IsSealed)
.Where(x => IsInterface || x.IsVirtual || x.IsAbstract)
.Select(x => (x, (x.Type as INamedTypeSymbol)?.DelegateInvokeMethod))
.Where(x => x.DelegateInvokeMethod is not null)
.Select(x => new Event(x.x, x.DelegateInvokeMethod!, alreadyDefinedEvents))
.Select(x => (x, x.Type as INamedTypeSymbol))
.Where(x => x.Item2?.DelegateInvokeMethod is not null)
.Select(x => new Event(x.x, x.Item2!.DelegateInvokeMethod!, alreadyDefinedEvents))
.Distinct(), exceptEvents, Event.ContainingTypeIndependentEqualityComparer);
Events = new EquatableArray<Event>(events.ToArray());

Expand All @@ -66,9 +66,9 @@ public Class(ITypeSymbol type,
.ToList();
exceptEvents ??= type.GetMembers().OfType<IEventSymbol>()
.Where(x => x.IsSealed)
.Select(x => (x, (x.Type as INamedTypeSymbol)?.DelegateInvokeMethod))
.Where(x => x.DelegateInvokeMethod is not null)
.Select(x => new Event(x.x, x.DelegateInvokeMethod!, null))
.Select(x => (x, x.Type as INamedTypeSymbol))
.Where(x => x.Item2?.DelegateInvokeMethod is not null)
.Select(x => new Event(x.x, x.Item2!.DelegateInvokeMethod!, null))
.Distinct()
.ToList();

Expand Down Expand Up @@ -146,7 +146,6 @@ private static bool TryExtractSpecialName(INamedTypeSymbol namedType, [NotNullWh
{
(specialName, bool hasSpecialType) = namedType.SpecialType switch
{
SpecialType.System_Void => ("void", true),
SpecialType.System_Object => ("object", true),
SpecialType.System_Boolean => ("bool", true),
SpecialType.System_String => ("string", true),
Expand Down
39 changes: 11 additions & 28 deletions Source/Mockolate.SourceGenerators/Entities/GenericParameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ internal readonly record struct GenericParameter
public GenericParameter(ITypeParameterSymbol typeSymbol)
{
Name = typeSymbol.Name;
IsUnmanaged = typeSymbol.HasUnmanagedTypeConstraint;
IsClass = typeSymbol.HasReferenceTypeConstraint;
IsStruct = typeSymbol.HasValueTypeConstraint;
IsNotNull = typeSymbol.HasNotNullConstraint;
IsUnmanaged = typeSymbol.HasUnmanagedTypeConstraint;
HasConstructor = typeSymbol.HasConstructorConstraint;
AllowsRefStruct = typeSymbol.AllowsRefLikeType;
NullableAnnotation = typeSymbol.ReferenceTypeConstraintNullableAnnotation;
Expand Down Expand Up @@ -43,23 +43,20 @@ public void AppendWhereConstraint(StringBuilder sb, string prefix)

int count = 0;
sb.AppendLine().Append(prefix).Append("where ").Append(Name).Append(" : ");
if (IsStruct)
{
if (count++ > 0)
{
sb.Append(", ");
}

if (IsUnmanaged)
{
count++;
sb.Append("unmanaged");
}
else if (IsStruct)
{
count++;
sb.Append("struct");
}

if (IsClass)
else if (IsClass)
{
if (count++ > 0)
{
sb.Append(", ");
}

count++;
sb.Append("class");
if (NullableAnnotation == NullableAnnotation.Annotated)
{
Expand All @@ -77,16 +74,6 @@ public void AppendWhereConstraint(StringBuilder sb, string prefix)
sb.Append("notnull");
}

if (IsUnmanaged)
{
if (count++ > 0)
{
sb.Append(", ");
}

sb.Append("unmanaged");
}

if (AllowsRefStruct)
{
if (count++ > 0)
Expand All @@ -105,10 +92,6 @@ public void AppendWhereConstraint(StringBuilder sb, string prefix)
}

sb.Append(constraintType.Fullname);
if (NullableAnnotation == NullableAnnotation.Annotated)
{
sb.Append('?');
}
}

if (HasConstructor)
Expand Down
12 changes: 0 additions & 12 deletions Source/Mockolate.SourceGenerators/Entities/Type.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,6 @@ internal Type(ITypeSymbol typeSymbol)
// Removes '*' from multi-dimensional array types
Fullname = typeSymbol.ToDisplayString().Replace("*", "");
Namespace = typeSymbol.ContainingNamespace?.ToString();
IsArray = typeSymbol.TypeKind == TypeKind.Array;
if (typeSymbol is IArrayTypeSymbol arrayType)
{
ElementType = new Type(arrayType.ElementType);
}

IsTypeParameter = typeSymbol.TypeKind == TypeKind.TypeParameter;
if (typeSymbol is INamedTypeSymbol namedTypeSymbol)
{
if (typeSymbol.IsTupleType)
Expand All @@ -40,17 +33,12 @@ internal Type(ITypeSymbol typeSymbol)
}

public SpecialGenericType SpecialGenericType { get; }

public bool IsArray { get; }
public bool IsTypeParameter { get; }
public EquatableArray<Type>? TupleTypes { get; }
public EquatableArray<Type>? GenericTypeParameters { get; }
public string? Namespace { get; }

internal static Type Void { get; } = new("void");

public string Fullname { get; }
public Type? ElementType { get; }

public override string ToString() => Fullname;
}
73 changes: 68 additions & 5 deletions Tests/Mockolate.SourceGenerators.Tests/GeneralTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,65 @@ await That(result.Sources)
.ContainsKey("MockForIMyInterface3.g.cs");
}

[Fact]
public async Task MockOfGenericTypes_ShouldHandleSpecialGenericParameters()
{
GeneratorResult result = Generator
.Run("""
using System;
using System.Collections.Generic;
using Mockolate;

namespace MyCode
{
public class Program
{
public static void Main(string[] args)
{
_ = Mockolate.Mock.Create<IMyInterface>();
}
}

public interface IMyInterface
{
IEnumerable<object> EnumerableOfObject { get; }
IEnumerable<bool> EnumerableOfBool { get; }
IEnumerable<string> EnumerableOfString { get; }
IEnumerable<char> EnumerableOfChar { get; }
IEnumerable<byte> EnumerableOfByte { get; }
IEnumerable<sbyte> EnumerableOfSbyte { get; }
IEnumerable<short> EnumerableOfShort { get; }
IEnumerable<ushort> EnumerableOfUshort { get; }
IEnumerable<int> EnumerableOfInt { get; }
IEnumerable<uint> EnumerableOfUint { get; }
IEnumerable<long> EnumerableOfLong { get; }
IEnumerable<ulong> EnumerableOfUlong { get; }
IEnumerable<float> EnumerableOfFloat { get; }
IEnumerable<double> EnumerableOfDouble { get; }
IEnumerable<decimal> EnumerableOfDecimal { get; }
}
}

""", typeof(IEnumerable<>));

await That(result.Sources).ContainsKey("MockForIMyInterface.g.cs").WhoseValue
.Contains("public System.Collections.Generic.IEnumerable<object> EnumerableOfObject").And
.Contains("public System.Collections.Generic.IEnumerable<bool> EnumerableOfBool").And
.Contains("public System.Collections.Generic.IEnumerable<string> EnumerableOfString").And
.Contains("public System.Collections.Generic.IEnumerable<char> EnumerableOfChar").And
.Contains("public System.Collections.Generic.IEnumerable<byte> EnumerableOfByte").And
.Contains("public System.Collections.Generic.IEnumerable<sbyte> EnumerableOfSbyte").And
.Contains("public System.Collections.Generic.IEnumerable<short> EnumerableOfShort").And
.Contains("public System.Collections.Generic.IEnumerable<ushort> EnumerableOfUshort").And
.Contains("public System.Collections.Generic.IEnumerable<int> EnumerableOfInt").And
.Contains("public System.Collections.Generic.IEnumerable<uint> EnumerableOfUint").And
.Contains("public System.Collections.Generic.IEnumerable<long> EnumerableOfLong").And
.Contains("public System.Collections.Generic.IEnumerable<ulong> EnumerableOfUlong").And
.Contains("public System.Collections.Generic.IEnumerable<float> EnumerableOfFloat").And
.Contains("public System.Collections.Generic.IEnumerable<double> EnumerableOfDouble").And
.Contains("public System.Collections.Generic.IEnumerable<decimal> EnumerableOfDecimal");
}

[Fact]
public async Task MockOfHttpMessageHandler_ShouldContainGetEnumeratorFromIEnumerableAndIEnumerableOfT()
{
Expand Down Expand Up @@ -405,7 +464,7 @@ public interface IMyService
string SomeProperty { get; set; }
[Localizable(false)]
string MyMethod(string message);
[CustomAttribute(
[CustomAttributeWithWrongName(
true,
(byte)42,
'X',
Expand All @@ -422,6 +481,7 @@ public interface IMyService
typeof(string),
MyEnum.Value2,
new int[] { 1, 2, 3 },
null,
BoolParam = false,
ByteParam = (byte)99,
CharParam = 'Y',
Expand All @@ -438,7 +498,8 @@ public interface IMyService
ObjectParam = 42,
TypeParam = typeof(int),
EnumParam = MyFlagEnum.Value1 | MyFlagEnum.Value2,
ArrayParam = new string[] { "a", "b" }
ArrayParam = new string[] { "a", "b" },
OptionalIntParam = null
)]
event EventHandler<int> MyEvent;
}
Expand All @@ -457,7 +518,7 @@ public enum MyFlagEnum
}

[AttributeUsage(AttributeTargets.All)]
public class CustomAttribute : Attribute
public class CustomAttributeWithWrongName : Attribute
{
public CustomAttribute(
bool boolArg,
Expand All @@ -475,7 +536,8 @@ public CustomAttribute(
ushort ushortArg,
Type typeArg,
MyEnum enumArg,
int[] arrayArg)
int[] arrayArg,
int? optionalIntArg)
{
}

Expand All @@ -496,6 +558,7 @@ public CustomAttribute(
public Type TypeParam { get; set; }
public MyFlagEnum EnumParam { get; set; }
public string[] ArrayParam { get; set; }
public int? OptionalIntParam { get; set; }
}
""", typeof(AllowNullAttribute), typeof(IDataParameter), typeof(LocalizableAttribute),
typeof(AttributeUsageAttribute));
Expand Down Expand Up @@ -541,7 +604,7 @@ public string MyMethod(string message)
""").IgnoringNewlineStyle().And
.Contains("""
/// <inheritdoc cref="MyCode.IMyService.MyEvent" />
[MyCode.Custom(true, (byte)42, 'X', 3.14, 2.71F, 100, 999L, (sbyte)-10, (short)500, "test", 123u, 456uL, (ushort)789, typeof(string), (MyCode.MyEnum)2, new int[]{1, 2, 3}, BoolParam = false, ByteParam = (byte)99, CharParam = 'Y', DoubleParam = 1.23, FloatParam = 4.56F, IntParam = 200, LongParam = 888L, SByteParam = (sbyte)-5, ShortParam = (short)300, StringParam = "named", UIntParam = 111u, ULongParam = 222uL, UShortParam = (ushort)333, ObjectParam = 42, TypeParam = typeof(int), EnumParam = (MyCode.MyFlagEnum)3, ArrayParam = new string[]{"a", "b"})]
[MyCode.CustomAttributeWithWrongName(true, (byte)42, 'X', 3.14, 2.71F, 100, 999L, (sbyte)-10, (short)500, "test", 123u, 456uL, (ushort)789, typeof(string), (MyCode.MyEnum)2, new int[]{1, 2, 3}, null, BoolParam = false, ByteParam = (byte)99, CharParam = 'Y', DoubleParam = 1.23, FloatParam = 4.56F, IntParam = 200, LongParam = 888L, SByteParam = (sbyte)-5, ShortParam = (short)300, StringParam = "named", UIntParam = 111u, ULongParam = 222uL, UShortParam = (ushort)333, ObjectParam = 42, TypeParam = typeof(int), EnumParam = (MyCode.MyFlagEnum)3, ArrayParam = new string[]{"a", "b"}, OptionalIntParam = null)]
public event System.EventHandler<int>? MyEvent
{
add
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,53 @@ public IVoidMethodSetup<int, int, int> Delegate(IParameter<int>? x, IRefParamete
""").IgnoringNewlineStyle();
}

[Fact]
public async Task CustomDelegates_ShouldSupportSpanAndReadOnlySpanParameters()
{
GeneratorResult result = Generator
.Run("""
using System;
using Mockolate;

namespace MyCode;

public class Program
{
public static void Main(string[] args)
{
_ = Mock.Create<DoSomething1>();
_ = Mock.Create<DoSomething2>();
}

public delegate Span<char> DoSomething1(int x);
public delegate ReadOnlySpan<char> DoSomething2(int x);
}
""");

await That(result.Sources)
.ContainsKey("MockForProgramDoSomething1.g.cs").WhoseValue
.Contains("""
public MyCode.Program.DoSomething1 Object => new(Invoke);
private System.Span<char> Invoke(int x)
{
var result = _mock.Registrations.InvokeMethod<System.Span<char>>("MyCode.Program.DoSomething1.Invoke", p => _mock.Registrations.Behavior.DefaultValue.Generate(default(SpanWrapper<char>)!, () => _mock.Registrations.Behavior.DefaultValue.Generate(default(char)!), p), x);
result.TriggerCallbacks(x);
return result.Result;
}
""").IgnoringNewlineStyle();
await That(result.Sources)
.ContainsKey("MockForProgramDoSomething2.g.cs").WhoseValue
.Contains("""
public MyCode.Program.DoSomething2 Object => new(Invoke);
private System.ReadOnlySpan<char> Invoke(int x)
{
var result = _mock.Registrations.InvokeMethod<System.ReadOnlySpan<char>>("MyCode.Program.DoSomething2.Invoke", p => _mock.Registrations.Behavior.DefaultValue.Generate(default(ReadOnlySpanWrapper<char>)!, () => _mock.Registrations.Behavior.DefaultValue.Generate(default(char)!), p), x);
result.TriggerCallbacks(x);
return result.Result;
}
""").IgnoringNewlineStyle();
}

[Fact]
public async Task Delegates_ShouldCreateDelegateInsteadOfMockSubject()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -625,12 +625,15 @@ await That(result.Sources).ContainsKey("MockForIMyService.g.cs").WhoseValue
[InlineData("class, T")]
[InlineData("struct")]
[InlineData("class")]
[InlineData("class, notnull")]
[InlineData("notnull")]
[InlineData("unmanaged")]
[InlineData("class?")]
[InlineData("MyCode.IMyInterface")]
[InlineData("new()")]
[InlineData("MyCode.IMyInterface?")]
[InlineData("allows ref struct")]
[InlineData("class, allows ref struct")]
[InlineData("MyCode.IMyInterface, new()")]
public async Task Methods_Generic_ShouldApplyAllConstraints(string constraint)
{
Expand Down
Loading