Skip to content

Commit

Permalink
SAVEPOINT
Browse files Browse the repository at this point in the history
  • Loading branch information
dennisdoomen committed Oct 19, 2024
1 parent a8e7f17 commit 8bbb5af
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 3 deletions.
3 changes: 2 additions & 1 deletion Src/FluentAssertions/Equivalency/MemberVisibility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ public enum MemberVisibility
None = 0,
Internal = 1,
Public = 2,
ExplicitlyImplemented = 4
ExplicitlyImplemented = 4,
DefaultInterfaceProperties = 8
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
#if NETCOREAPP3_0_OR_GREATER
using System;
using System.Collections.Generic;
using System.Reflection;
using FluentAssertions.Equivalency;
using Xunit;

namespace FluentAssertions.Specs.Common;

public partial class TypeExtensionsSpecs
{
public class GetProperties
{
[Fact]
public void Can_get_all_public_explicit_and_default_interface_properties()
{
// Act
var properties = typeof(SuperClass)
.GetProperties2(MemberVisibility.Public | MemberVisibility.ExplicitlyImplemented |
MemberVisibility.DefaultInterfaceProperties);

// Assert
properties.Should().BeEquivalentTo(new[]
{
new { Name = "NormalProperty", PropertyType = typeof(string) },
new { Name = "NewProperty", PropertyType = typeof(int) },
new { Name = "InterfaceProperty", PropertyType = typeof(string) },
new
{
Name = $"{typeof(IInterfaceWithSingleProperty).FullName!.Replace("+", ".")}.ExplicitlyImplementedProperty",
PropertyType = typeof(string)
},
new { Name = "DefaultProperty", PropertyType = typeof(string) }
});
}

[Fact]
public void Can_get_normal_public_properties()
{
// Act
var properties = typeof(SuperClass)
.GetProperties2(MemberVisibility.Public);

// Assert
properties.Should().BeEquivalentTo(new[]
{
new { Name = "NormalProperty", PropertyType = typeof(string) },
new { Name = "NewProperty", PropertyType = typeof(int) },
new { Name = "InterfaceProperty", PropertyType = typeof(string) },
});
}

[Fact]
public void Can_get_explicit_properties_only()
{
// Act
var properties = typeof(SuperClass)
.GetProperties2(MemberVisibility.ExplicitlyImplemented);

// Assert
properties.Should().BeEquivalentTo(new[]
{
new
{
Name = $"{typeof(IInterfaceWithSingleProperty).FullName!.Replace("+", ".")}.ExplicitlyImplementedProperty",
PropertyType = typeof(string)
},
});
}

[Fact]
public void Can_get_default_interface_properties_only()
{
// Act
var properties = typeof(SuperClass)
.GetProperties2(MemberVisibility.DefaultInterfaceProperties);

// Assert
properties.Should().BeEquivalentTo(new[]
{
new { Name = "DefaultProperty", PropertyType = typeof(string) },
});
}

[Fact]
public void Can_get_internal_properties()
{
// Act
var properties = typeof(SuperClass)
.GetProperties2(MemberVisibility.Internal);

// Assert
properties.Should().BeEquivalentTo(new[]
{
new { Name = "InternalProperty", PropertyType = typeof(bool) },
});
}

private class SuperClass : BaseClass, IInterfaceWithDefaultProperty
{
public string NormalProperty { get; set; }

public new int NewProperty { get; set; }

internal bool InternalProperty { get; set; }

string IInterfaceWithSingleProperty.ExplicitlyImplementedProperty { get; set; }

public string InterfaceProperty { get; set; }
}

private class BaseClass
{
public string NewProperty { get; set; }
}

private interface IInterfaceWithDefaultProperty : IInterfaceWithSingleProperty
{
string InterfaceProperty { get; set; }

string DefaultProperty => "Default";
}

private interface IInterfaceWithSingleProperty
{
string ExplicitlyImplementedProperty { get; set; }
}
}
}

internal static class TypeReflector
{
public static PropertyInfo[] GetProperties2(this Type type, MemberVisibility visibility)
{
// start with type
// iterate over all properties (including new)
// add explicitly implemented properties
// for each interface in the graph, recursively add default properties
// continue with base until base = object
var collectedProperties = new HashSet<string>();
var properties = new List<PropertyInfo>();

// Start with the given type and iterate up the inheritance chain
while (type != null && type != typeof(object))
{
// Add all properties declared in the current type (including new ones)
if (visibility.HasFlag(MemberVisibility.Public) || visibility.HasFlag(MemberVisibility.Internal) ||
visibility.HasFlag(MemberVisibility.ExplicitlyImplemented))
{
foreach (var prop in type
.GetProperties(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public |
BindingFlags.NonPublic))
{
if (!collectedProperties.Contains(prop.Name) && (HasVisibility(visibility, prop) ||
(visibility.HasFlag(MemberVisibility.ExplicitlyImplemented) && IsExplicitlyImplemented(prop))))
{
properties.Add(prop);
collectedProperties.Add(prop.Name);
}
}
}

if (visibility.HasFlag(MemberVisibility.DefaultInterfaceProperties))
{
// Add explicitly implemented interface properties (not included above)
var interfaces = type.GetInterfaces();

foreach (var iface in interfaces)
{
foreach (var prop in iface.GetProperties())
{
if (!collectedProperties.Contains(prop.Name))
{
properties.Add(prop);
collectedProperties.Add(prop.Name);
}
}
}
}

// Move to the base type
type = type.BaseType;
}

return properties.ToArray();
}

private static bool IsExplicitlyImplemented(PropertyInfo prop)
{
return prop.Name.Contains('.', StringComparison.InvariantCultureIgnoreCase);
}

private static bool HasVisibility(MemberVisibility visibility, PropertyInfo prop) =>
(visibility.HasFlag(MemberVisibility.Public) && prop.GetMethod?.IsPublic is true) ||
(visibility.HasFlag(MemberVisibility.Internal) && prop.GetMethod?.IsAssembly is true);
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
using FluentAssertions.Common;
using Xunit;

namespace FluentAssertions.Specs.Types;
namespace FluentAssertions.Specs.Common;

public class TypeExtensionsSpecs
public partial class TypeExtensionsSpecs
{
[Fact]
public void When_comparing_types_and_types_are_same_it_should_return_true()
Expand Down
1 change: 1 addition & 0 deletions Tests/FluentAssertions.Specs/FluentAssertions.Specs.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<IsPackable>false</IsPackable>
<NoWarn>$(NoWarn),IDE0052,1573,1591,1712,CS8002</NoWarn>
<DebugType>full</DebugType>
<LangVersion>12</LangVersion>
</PropertyGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.0' ">
Expand Down

0 comments on commit 8bbb5af

Please sign in to comment.