Skip to content
Open
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
11 changes: 11 additions & 0 deletions ArchUnitNET/Domain/Architecture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@ public Architecture(
public IEnumerable<FieldMember> FieldMembers => Members.OfType<FieldMember>();
public IEnumerable<MethodMember> MethodMembers => Members.OfType<MethodMember>();
public IEnumerable<IMember> Members => Types.SelectMany(type => type.Members);
public IEnumerable<FieldMember> ReferencedFieldMembers =>
ReferencedMembers.OfType<FieldMember>();
public IEnumerable<MethodMember> ReferencedMethodMembers =>
ReferencedMembers.OfType<MethodMember>();

/// <summary>
/// The members of referenced types that the loaded types use, such as a framework method
/// they call or a framework field they read. A referenced type carries no other members.
/// </summary>
public IEnumerable<IMember> ReferencedMembers =>
ReferencedTypes.SelectMany(type => type.Members);

/// <summary>
/// Returns the cached result for the given object provider, or invokes the providing
Expand Down
28 changes: 18 additions & 10 deletions ArchUnitNET/Fluent/ArchRuleDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,25 +50,33 @@ public static GivenInterfaces Interfaces(bool includeReferenced = false)
return new GivenInterfaces(ruleCreator);
}

public static GivenMembers Members()
public static GivenMembers Members(bool includeReferenced = false)
{
var ruleCreator = new ArchRuleCreator<IMember>(BasicObjectProviderDefinition.Members);
var ruleCreator = includeReferenced
? new ArchRuleCreator<IMember>(
BasicObjectProviderDefinition.MembersIncludingReferenced
)
: new ArchRuleCreator<IMember>(BasicObjectProviderDefinition.Members);
return new GivenMembers(ruleCreator);
}

public static GivenFieldMembers FieldMembers()
public static GivenFieldMembers FieldMembers(bool includeReferenced = false)
{
var ruleCreator = new ArchRuleCreator<FieldMember>(
BasicObjectProviderDefinition.FieldMembers
);
var ruleCreator = includeReferenced
? new ArchRuleCreator<FieldMember>(
BasicObjectProviderDefinition.FieldMembersIncludingReferenced
)
: new ArchRuleCreator<FieldMember>(BasicObjectProviderDefinition.FieldMembers);
return new GivenFieldMembers(ruleCreator);
}

public static GivenMethodMembers MethodMembers()
public static GivenMethodMembers MethodMembers(bool includeReferenced = false)
{
var ruleCreator = new ArchRuleCreator<MethodMember>(
BasicObjectProviderDefinition.MethodMembers
);
var ruleCreator = includeReferenced
? new ArchRuleCreator<MethodMember>(
BasicObjectProviderDefinition.MethodMembersIncludingReferenced
)
: new ArchRuleCreator<MethodMember>(BasicObjectProviderDefinition.MethodMembers);
return new GivenMethodMembers(ruleCreator);
}

Expand Down
20 changes: 20 additions & 0 deletions ArchUnitNET/Fluent/BasicObjectProviderDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,25 @@ public static class BasicObjectProviderDefinition
architecture => architecture.Interfaces.Concat(architecture.ReferencedInterfaces),
"Interfaces (including referenced)"
);

public static readonly BasicObjectProvider<IMember> MembersIncludingReferenced =
new BasicObjectProvider<IMember>(
architecture => architecture.Members.Concat(architecture.ReferencedMembers),
"Members (including referenced)"
);

public static readonly BasicObjectProvider<FieldMember> FieldMembersIncludingReferenced =
new BasicObjectProvider<FieldMember>(
architecture =>
architecture.FieldMembers.Concat(architecture.ReferencedFieldMembers),
"Field members (including referenced)"
);

public static readonly BasicObjectProvider<MethodMember> MethodMembersIncludingReferenced =
new BasicObjectProvider<MethodMember>(
architecture =>
architecture.MethodMembers.Concat(architecture.ReferencedMethodMembers),
"Method members (including referenced)"
);
}
}
7 changes: 7 additions & 0 deletions ArchUnitNET/Loader/ArchBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,13 @@ from entry in typesToProcess
TypeProcessor.AddTypesToNamespaces(
_typesToProcess.Values.Select(entry => entry.TypeInstance.Type)
);

// Phase 12: Register referenced members with their referenced types
TypeProcessor.AddReferencedMembers(
_domainResolver
.MethodMemberInstances.Select(instance => (IMember)instance.Member)
.Concat(_domainResolver.FieldMembers)
);
}
}
}
10 changes: 10 additions & 0 deletions ArchUnitNET/Loader/DomainResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ internal class DomainResolver
/// </summary>
public IEnumerable<ITypeInstance<IType>> Types => _allTypes.Values;

/// <summary>
/// All method members that have been created or cached, including generic method instances.
/// </summary>
internal IEnumerable<MethodMemberInstance> MethodMemberInstances => _allMethods.Values;

/// <summary>
/// All field members that have been created or cached.
/// </summary>
internal IEnumerable<FieldMember> FieldMembers => _allFields.Values;

/// <summary>
/// Returns the existing <see cref="Assembly"/> for the given full name, or creates
/// and caches a new one.
Expand Down
24 changes: 24 additions & 0 deletions ArchUnitNET/Loader/TypeProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1122,6 +1122,30 @@ internal static void AddTypesToNamespaces(IEnumerable<IType> types)
((List<IType>)type.Namespace.Types).Add(type);
}
}

// ── Phase 12: Register referenced members with referenced types ─────

/// <summary>
/// Adds each member the loaded types reference on a referenced type to that type's members.
/// Referenced types never pass through <see cref="AddMembers"/>, so without this step a
/// member such as a called framework method is reachable from its call site but not from
/// its declaring type. Must run after method body dependencies, which create these members.
/// </summary>
internal static void AddReferencedMembers(IEnumerable<IMember> members)
{
foreach (var member in members.Distinct())
{
var declaringType = member.DeclaringType;
if (
declaringType.IsStub
&& !declaringType.IsGenericParameter
&& !member.IsCompilerGenerated
)
{
declaringType.Members.Add(member);
}
}
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using ArchUnitNET.Domain;
using System;
using ArchUnitNET.Domain;
using ArchUnitNET.Domain.Extensions;
using ArchUnitNET.Loader;
using ArchUnitNET.xUnit;
Expand Down Expand Up @@ -128,6 +129,66 @@ public void IncludeReferencedTypesTest()
Assert.Contains(interfaceInTestAssembly, Types(true).GetObjects(Architecture));
Assert.Contains(interfaceInTestAssembly, Interfaces(true).GetObjects(Architecture));
}

[Fact]
public void IncludeReferencedMembersTest()
{
var classInTestAssembly = Architecture.GetClassOfType(typeof(ClassInTestAssembly));
var method = Assert.Single(
classInTestAssembly.GetMethodMembersWithName("MethodInTestAssembly()")
);
var field = Assert.Single(
classInTestAssembly.GetFieldMembersWithName("FieldInTestAssembly")
);

Assert.Contains(method, MethodMembers(true).GetObjects(Architecture));
Assert.Contains(method, Members(true).GetObjects(Architecture));
Assert.Contains(field, FieldMembers(true).GetObjects(Architecture));
Assert.Contains(field, Members(true).GetObjects(Architecture));

Assert.DoesNotContain(method, MethodMembers().GetObjects(Architecture));
Assert.DoesNotContain(field, FieldMembers().GetObjects(Architecture));
Assert.DoesNotContain(method, Members().GetObjects(Architecture));
}

[Fact]
public void ReferencedTypeHasOnlyTheMembersLoadedTypesUseTest()
{
var classInTestAssembly = Architecture.GetClassOfType(typeof(ClassInTestAssembly));

Assert.Empty(
classInTestAssembly.GetMethodMembersWithName("UnusedMethodInTestAssembly()")
);
Assert.All(
Architecture.ReferencedMembers,
member => Assert.True(member.DeclaringType.IsStub)
);
}

[Fact]
public void NotCallAnyReferencedMethodTest()
{
var getNow = MethodMembers(true)
.That()
.AreDeclaredIn(typeof(DateTime))
.And()
.HaveName("get_Now()");

Types()
.That()
.Are(typeof(DependingClass))
.Should()
.CallAny(getNow)
.Check(Architecture);
Assert.Throws<FailedArchRuleException>(() =>
Types()
.That()
.Are(typeof(DependingClass))
.Should()
.NotCallAny(getNow)
.Check(Architecture)
);
}
}
}

Expand All @@ -140,6 +201,8 @@ public DependingClass()
{
dep = new ClassInTestAssembly();
dep.MethodInTestAssembly();
_ = dep.FieldInTestAssembly;
_ = DateTime.Now;
}
}
}
4 changes: 4 additions & 0 deletions TestAssembly/DependencyTargets/ClassInTestAssembly.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
{
public class ClassInTestAssembly
{
public int FieldInTestAssembly;

public void MethodInTestAssembly() { }

public void UnusedMethodInTestAssembly() { }
}
}
Loading