ArchUnitNET icon indicating copy to clipboard operation
ArchUnitNET copied to clipboard

How to check for system members?

Open simonthum opened this issue 2 years ago • 3 comments

I have difficulties checking for unwanted system references - I failed to jot them down in a statically typed way.

Consider:

IArchRule rule1 =
    Classes().Should().NotCallAny("System.DateTime::get_Now");

This works but is not exactly intuitive. I went on to try:

IArchRule rule2 =
    Classes().Should().NotCallAny(
    MethodMembers().That().AreDeclaredIn(
        Types(true).That().HaveFullName(typeof(DateTime).FullName)));

But there is no MethodMembers(true) that would allow to check for members of system types this way.

Is there another way I'm missing to achieve rule1 statically typed?

simonthum avatar Dec 11 '22 13:12 simonthum

I would also like to implement a test that only certain types can access the system time. However, the solution mentioned by @simonthum unfortunately does not work for me.

I tried unsuccessfully with Classes().Should().NotCallAny(MethodMembers().That().AreDeclaredIn(typeof(System.DateTimeOffset)));

However, this is reasonable to me since _ = DateTimeOffset.Now is an access to a property. However, I cannot replace MethodMembers with PropertyMembers because the result is not accepted by NotCallAny.

I wonder if there is a way to check for property access, or is this functionality not (yet) part of ArchUnitNET?

x789 avatar Feb 28 '23 17:02 x789

I was wrong -- Properties are a C# feature which are transformed into methods by the compiler. After specifying an empty parameter list and adding the assembly of DateTimeOffset to the architecture, it worked:

var rule = Types().That().ResideInAssembly(myAssembly).Should().NotCallAny("System.DateTimeOffset::get_Now()");

I didn't find a way to define the rule strongly typed either. For the meantime I'm using an extension method. Maybe this is an acceptable workaround for others.

// Extension method
public static IArchRule NotAccessGetter<TType>(this ObjectsShould<TypesShouldConjunction, IType> builder, object? property, [CallerArgumentExpression("property")] string typeAndPropertyName = null!)
{
  var names = typeAndPropertyName.Split('.');
  if (names[0] != typeof(TType).Name) throw new ArgumentException($"'{names[1]}' is not part of '{names[0]}'.");

  return builder.NotCallAny(MethodMembers().That().AreDeclaredIn(typeof(TType)).And().HaveName($"get_{names[1]}()"));
}
// Usage example
[Fact]
public void Test1()
{
  // Remember to add the assembly that contains DateTimeOffset to your `architecture`.
  var types = Types().That().ResideInAssembly(myAssembly);
  types.Should().NotAccessGetter<DateTimeOffset>(DateTimeOffset.Now).Check(architecture);
  types.Should().NotAccessGetter<DateTimeOffset>(DateTimeOffset.UtcNow).Check(architecture);
}

x789 avatar Mar 02 '23 17:03 x789

@x789 I like that creative solution, although having to actually call the Property may make it difficult to use in some contexts.

simonthum avatar Mar 20 '23 22:03 simonthum